Spring’s @Transactional is a great idea to reduce the boiler plate code usually necessary to begin and end a transaction:
Without @Transactional in pseudo-code:
public void saveOrder(Order order) {
transaction = transactionManager.beginTransaction();
try {
// here: save the order
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
}
}
With @Transactional:
@Transactional
public void saveOrder(Order order) {
// here: save the order
}
Both methods execute the same code. However, the second implementation is much shorter.
How does it work? Let’s quote a short description:
At a high level, Spring creates proxies for all the classes annotated with @Transactional, either on the class or on any of the methods. The proxy allows the framework to inject transactional logic before and after the running method, mainly for starting and committing the transaction.
https://www.baeldung.com/transaction-configuration-with-jpa-and-spring#1-transactions-and-proxies
The following figure illustrates this approach:
When invoking saveOrder(..) from within some external method, saveOrder(..) of the proxy class is called. The proxy starts the transaction and then delegates to the original saveOrder(..). After returning, the proxy commits or rollbacks the transaction.
This approach has some important implications. Let’s quote again:
What’s important to keep in mind is that, if the transactional bean is implementing an interface, by default the proxy will be a Java Dynamic Proxy. This means that only external method calls that come in through the proxy will be intercepted. Any self-invocation calls will not start any transaction, even if the method has the @Transactional annotation.
Another caveat of using proxies is that only public methods should be annotated with @Transactional. Methods of any other visibilities will simply ignore the annotation silently as these are not proxied.
https://www.baeldung.com/transaction-configuration-with-jpa-and-spring#1-transactions-and-proxies
With regard to our figure from above, the call from within updateOrder(..) to saveOrder(..) will not start any transaction since it is invoked from within the same class. Furthermore, a call to deleteOrder(..) will not start any transaction either, since it is not public.
Do not use a “self” proxy reference in this case. It’s hard to understand. Share a private method instead. Or copy and paste the corresponding code section. For details, read https://codete.com/blog/5-common-spring-transactional-pitfalls.
Rollbacks
By default only unchecked exceptions trigger rollbacks. So, try to avoid throwing checked exceptions within transactions. For details, read For details, read https://codete.com/blog/5-common-spring-transactional-pitfalls.
References
- https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html#transaction-declarative-rolling-back
- https://blog.frankel.ch/a-spring-hard-fact-about-transaction-management/
- https://stackoverflow.com/a/23934667
- https://codete.com/blog/5-common-spring-transactional-pitfalls/