Question

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:

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!

Était-ce utile?

La solution

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.

Autres conseils

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.

Sometimes you want to roll back a transaction when an exception is thrown, but at the same time you want to handle the exception create a proper response in the controller to it.

If you put @Transactional on the controller method the only way to enforce the rollback it to throw the transaction from the controller method, but then you cannot return a normal response object.

Update: A rollback can also be achieved programmatically, as outlined in Rodério's answer.

A better solution is to make your service method transactional and then handle a possible exception in the controller methods.

The following example shows a user service with a createUser method, that method is responsible to create the user and send an email to the user. If sending the mail fails we want to rollback the user creation:

@Service
public class UserService {

    @Transactional
    public User createUser(Dto userDetails) {

        // 1. create user and persist to DB

        // 2. submit a confirmation mail
        //    -> might cause exception if mail server has an error

        // return the user
    }
}

Then in your controller you can wrap the call to createUser in a try/catch and create a proper response to the user:

@Controller
public class UserController {

    @RequestMapping
    public UserResultDto createUser (UserDto userDto) {

        UserResultDto result = new UserResultDto();

        try {

            User user = userService.createUser(userDto);

            // built result from user

        } catch (Exception e) {
            // transaction has already been rolled back.

            result.message = "User could not be created " + 
                             "because mail server caused error";
        }

        return result;
    }
}

If you put a @Transaction on your controller method that is simply not possible.

I think the best way is to put the transactional annotation at the layer where you transition from entities (persistent objects) to DTOs (transient objects). The reason for this is that entity relations may need to be traversed which may trigger lazy initializations and you don't want to use the open session in view anti-pattern.

One argument to put them at the controller level would be that REST already dictates the read-only or read-write properties of a transaction. (GET should be read-only and POST/PUT/DELETE should be read-write). Mind that this would only work if your exception to error response handling happens outside of the controller so a transaction is properly rolled back when this happens.

An argument to put them at the service layer is that the service probably relies on the proper transaction isolation/propagation for its inner workings.

Probably the best compromise is to design facade level services which only list DTO object in their API and contain the transaction annotations as part of their interface. It is important to design the interfaces with the transactional handling in mind, since calling a read-write method from a read-only method may have undesirable side effects.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top