Issue
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:
href="https://stackoverflow.com/questions/20385412/transaction-not-working-correctly-spring-mybatis">Transaction not working correctly - Spring/MyBatis
For web MVC Spring app should @Transactional go on controller or service?
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!
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 OpenSessionInView
or 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.
Answered By - Angular University
Answer Checked By - Pedro (JavaFixing Volunteer)