Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why we shouldn't make a Spring MVC controller @Transactional?

There are already a few questions about the topic, but no response at all really provides arguments in order to explain why we shouldn't make a Spring MVC controller Transactional. See:

  • Transaction not working correctly - Spring/MyBatis

  • For web MVC Spring app should @Transactional go on controller or service?

  • Making Spring 3 MVC controller method Transactional

  • Spring MVC Controller Transactional

So, why?

  • Is there insurmountable technical issues?
  • Is there architectural issues?
  • Is there performance/deadlock/concurrency issues?
  • Are sometimes multiple separate transactions required? If yes, what are the use cases? (I like the simplifying design, that calls to server either completely succeed or completely fail. It sounds to be a very stable behavior)

Background: I worked a few years ago in a Team on a quite large ERP Software implemented in C#/NHibernate/Spring.Net. The round-trip to the server was exactly implemented like that: the transaction was opened before entering any controller logic and was committed or rollbacked after exiting the controller. The transaction was managed in the framework so that no one had to care about it. It was a brilliant solution: stable, simple, only a few architects had to care about transaction issues, the rest of the team just implemented features.

From my point of view, it is the best design I have ever seen. As I tried to reproduce the same design with Spring MVC, I entered in a nightmare with lazy-loading and transaction issues and every time the same answer: don't make the controller transactional, but why?

Thank you in advance for your founded answers!

like image 999
jeromerg Avatar asked Apr 16 '14 19:04

jeromerg


People also ask

Why applying Spring @transactional annotation on the concrete class or its methods is recommended rather than on interface?

Spring's recommendation is that you annotate the concrete implementations instead of an interface. It's not incorrect to use the annotation on an interface, it's just possible to misuse that feature and inadvertently bypass your @Transaction declaration.

Do we need @transactional for read only?

The @Transactional annotation offers the readOnly attribute, which is false by default. The readOnly attribute can further be used by Spring to optimize the underlying data access layer operations.

What are the benefits of the Spring Framework's transaction management?

The Spring Framework provides a consistent abstraction for transaction management that delivers the following benefits: Consistent programming model across different transaction APIs such as Java Transaction API (JTA), JDBC, Hibernate, Java Persistence API (JPA), and Java Data Objects (JDO).

Can we use @transactional on private methods?

The answer your question is no - @Transactional will have no effect if used to annotate private methods. The proxy generator will ignore them. When using proxies, you should apply the @Transactional annotation only to methods with public visibility.


2 Answers

TLDR: this is because only the service layer in the application has the logic needed to identify the scope of a database/business transaction. The controller and persistence layer by design can't/shouldn't know the scope of a transaction.

The controller can be made @Transactional, but indeed it's a common recommendation to only make the service layer transactional (the persistence layer should not be transactional either).

The reason for this is not technical feasibility, but separation of concerns. The controller responsibility is to get the parameter requests, and then call one or more service methods and combine the results in a response that is then sent back to the client.

So the controller has a function of coordinator of the request execution, and transformer of the domain data to a format the client can consume such as DTOs.

The business logic resides on the service layer, and the persistence layer just retrieve / stores data back and forth from the database.

The scope of a database transaction is really a business concept as much as a technical concept: in an account transfer an account can only be debited if the other is credited etc., so only the service layer that contains the business logic can really know the scope of a bank account transfer transaction.

The persistence layer cannot know what transaction it's in, take for example a method customerDao.saveAddress. Should it run in it's own separate transaction always? there is no way to know, it depends on the business logic calling it. Sometimes it should run on a separate transaction, sometimes only save it's data if the saveCustomer also worked, etc.

The same applies to the controller: should saveCustomer and saveErrorMessages go in the same transaction? You might want to save the customer and if that fails then try to save some error messages and return a proper error message to the client, instead of rolling back everything including the error messages you wanted to save on the database.

In non transactional controllers, methods returning from the service layer return detached entities because the session is closed. This is normal, the solution is to either use OpenSessionInViewor do queries that eager fetch the results the controller knows it needs.

Having said that, it's not a crime to make controllers transactional, it's just not the most frequently used practice.

like image 155
Angular University Avatar answered Oct 22 '22 08:10

Angular University


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.

like image 38
Rogério Avatar answered Oct 22 '22 10:10

Rogério