I have seen both cases in practice, in medium- to large-sized business web applications, using various web frameworks (JSP/Struts 1.x, GWT, JSF 2, with Java EE and Spring).
In my experience, it's best to demarcate transactions at the highest level, ie, at the "controller" level.
In one case, we had a BaseAction
class extending Struts' Action
class, with an implementation for the execute(...)
method that handled Hibernate session management (saved into a ThreadLocal
object), transaction begin/commit/rollback, and the mapping of exceptions to user-friendly error messages. This method would simply rollback the current transaction if any exception got propagated up to this level, or if it was marked for rollback only; otherwise, it would commit the transaction. This worked in every case, where normally there is a single database transaction for the whole HTTP request/response cycle. Rare cases where multiple transactions were needed would be handled in use-case specific code.
In the case of GWT-RPC, a similar solution was implemented by a base GWT Servlet implementation.
With JSF 2, I've so far only used service-level demarcation (using EJB Session beans which automatically have "REQUIRED" transaction propagation). There are disadvantages here, as opposed to demarcating transactions at the level of the JSF backing beans. Basically, the problem is that in many cases the JSF controller needs to make several service calls, each one accessing the application database. With service-level transactions, this implies several separate transactions (all committed, unless an exception occurs), which taxes more the database server. It isn't just a performance disadvantage, though. Having multiple transactions for a single request/response can also lead to subtle bugs (I don't remember the details anymore, just that such issues did occur).
Other answer to this question talks about "logic needed to identify the scope of a database/business transaction". This argument doesn't make sense to me, since there is no logic associated with transaction demarcation at all, normally. Neither controller classes nor service classes need to actually "know" about transactions. In the vast majority of cases, in a web app each business operation occurs inside an HTTP request/response pair, with the scope of the transaction being all the individual operations being executed from the point the request is received up until the response being finished.
Occasionaly, a business service or controller may need to handle an exception in a particular way, then probably mark the current transaction for rollback only. In Java EE (JTA), this is done by calling UserTransaction#setRollbackOnly(). The UserTransaction
object can be injected into a @Resource
field, or obtained programmatically from some ThreadLocal
. In Spring, the @Transactional
annotation allows rollback to be specified for certain exception types, or code can obtain a thread-local TransactionStatus and call setRollbackOnly()
.
So, in my opinion and experience, making the controller transactional is the better approach.