Introduction
In my opinion transaction management is a really important topic for each backend developer. In general, people don’t pay attention to it while using Spring framework.
But I think, it is important to know how to use transactions properly. Because sometimes can happen that there was an exception thrown inside your method, but transaction was not rolled back and it is not clear why? Or some other “strange” cases.
@Transactional
In spring there is @Transactional annotation that can be used for wrapping a method in a transaction. We can use it with interfaces (lowest priority), classes or certain methods (highest priority).
@Transactional is applied only for public methods and method must be invoked from outside of the bean.
@Transactional annotation has multiple parameters. I would like to focus on two of them: Isolation and Propagation.
Isolation is one of the main properties of transactions (Atomicity, Consistency, Isolation, Durability). It describes visibility of changes applied by concurrent transactions to each other.
In Spring it is possible to set one of the 5 isolation level values: DEFAULT, READ_UNCOMMITTED, READ_COMMITED, REPETABLE_READ and SERIALIZABLE.
For example,
@Transactional (isolation=Isolation.READ_COMMITED)
Each of these isolation levels may have or haven’t different side effects: “dirty” read, non-repeatable read and phantom read. What each of them means?
“Dirty” read — one transaction can read changes of a concurrent transaction, that were not committed yet.
If you like more diagrams as I do:
Transaction T1 begins first, then we start transaction T2.
After that T1 changes row with id=10 in database and T2 reads it. Something wrong happens and T1 is rolled back. And recording in T2 is dirty now.
Non-repeatable read — one transaction reads the same row twice, but gets different values because between these reads the data was updated by the concurrent transaction.
Phantom read — one transaction runs the same query twice, but gets a different set of rows as result, because of the changes applied by another concurrent transaction.
Let’s go back to the isolation levels and check their possible side effects.
DEFAULT value is used when we want to use default isolation level of our RDBMS. Default value for PostgreSQL, Oracle and SQL server is READ_COMMITTED, for MySQL — REPEATABLE_READ.
with READ_UNCOMMITTED isolation level, we can have all three side effects
READ_COMMITTED isolation level has one change in comparison with READ_UNCOMMITTED — it prevents “dirty” reads.
REPEATABLE_READ prevents “dirty” and non-repeatable reads.
SERIALIZABLE isolation level prevents all mentioned above side effects. It performs all transactions in sequence.
Isolation level | Phantom read | Non-repeatable read | “Dirty” read |
---|---|---|---|
READ_UNCOMMITTED | possible | possible | possible |
READ_COMMITTED | possible | possible | not possible |
REPEATABLE_READ | possible | not possible | not possible |
SERIALIZABLE | not possible | not possible | not possible |
There is also another important parameter of @Transactional: propagation.
Propagation can be set to REQUIRED, REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NESTED or NEVER.
Example:
@Transactional (propagation=Propagation.REQUIRED)
REQUIRED propagation level uses an existing transaction if there is one. Otherwise creates a new transaction.
REQUIRES_NEW propagation level says to suspend outer transaction (if there is one) and create a new one.
MANDATORY propagation uses an existing transaction if there is one. Otherwise, an exception will be thrown.
SUPPORTS propagation level uses the current transaction if there is one. Otherwise, doesn’t create a new one.
NOT_SUPPORTED suspends current transaction if there is one.
NESTED creates nested transaction when there is an existing transaction already or works like REQUIRED if there is no transaction.
NEVER throws an exception if there is an active transaction.
Propagation | Calling method (outer) | Called method (inner) |
---|---|---|
REQUIRED | No | T2 |
REQUIRED | T1 | T1 |
REQUIRES_NEW | No | T2 |
REQUIRES_NEW | T1 | T2 |
MANDATORY | No | Exception |
MANDATORY | T1 | T1 |
NOT_SUPPORTED | No | No |
NOT_SUPPORTED | T1 | No |
SUPPORTS | No | No |
SUPPORTS | T1 | T1 |
NEVER | No | No |
NEVER | T1 | Exception |
NESTED | No | T2 |
NESTED | T1 | T2 |
Also interesting is that Spring framework does automatic transaction rollback only for unchecked (Runtime) exceptions. To change it we can use rollbackFor parameter.
For example, we can put
@Transactional (rollbackFor=Exception.class)
Conclusion
In the article, I tried to describe how to use Isolation and Propagation parameters of @Transactional in Spring.
I remember how several years ago I had some problems with the chain of transactional method and I hope that my article can help other developers to avoid them and have more clear understanding of the topic.