Pull to refresh

Spring transaction management. Isolation and propagation

Reading time 3 min
Views 25K

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.


  1. 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.


  2. with READ_UNCOMMITTED isolation level, we can have all three side effects


  3. READ_COMMITTED isolation level has one change in comparison with READ_UNCOMMITTED — it prevents “dirty” reads.


  4. REPEATABLE_READ prevents “dirty” and non-repeatable reads.


  5. 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)

  1. REQUIRED propagation level uses an existing transaction if there is one. Otherwise creates a new transaction.


  2. REQUIRES_NEW propagation level says to suspend outer transaction (if there is one) and create a new one.


  3. MANDATORY propagation uses an existing transaction if there is one. Otherwise, an exception will be thrown.


  4. SUPPORTS propagation level uses the current transaction if there is one. Otherwise, doesn’t create a new one.


  5. NOT_SUPPORTED suspends current transaction if there is one.


  6. NESTED creates nested transaction when there is an existing transaction already or works like REQUIRED if there is no transaction.


  7. 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.

Tags:
Hubs:
+6
Comments 0
Comments Leave a comment

Articles