Transaction Control in Spring Boot

What is a Transaction?

In the real world, we are surrounded by transactions every day. Financial ones, exchange, all interactions between 2 or more independent processes to produce the desired output could be considered as a transaction. Let’s explain it with a real-life example:

You have your mobile banking app and need to transfer some money to your friend. So, to do it, first, you open the application, authenticate your user, put your friend’s account number and transfer the money. Sounds simple, but what happened there?

  • User authentication
  • Your friend’s account validation
  • Validation of you have enough money to transfer
  • Discount money from your account
  • Add money for the destiny account

 

In 5 steps, we were able to transfer money. Also, each step is completely independent of the other, for example, the account validation is a completely different process from the discount money from your account, so, as explained before, we have the interaction of more than 1 independent process to produce the desired output, in this case, money in your friend’s account. This is the base transactional concept, but there is one more thing to keep in mind: What should happen if one of the processes fails? Should continue? Should discard the operation? For being considered as a transaction, all processes inside should be executed successfully. If one process fails, all transactions is failed. This is the transactional principle.

 A transaction must be committed only after the successful execution of the entire actions. Otherwise, the transaction should be rolled off in case of any exceptions. Below image depicts the same.

The @Transactional Annotation

With transactions configured, we can now annotate a bean with @Transactional either at the class or method level:

This service is tagged with @Transactional, meaning that any failure causes the entire operation to roll back to its previous state and to re-throw the original exception. This means that none of the values are added to database if there is any failure at the runtime.

Spring allows managing the isolation level. The default strategy for most databases is READ_COMMITTED, but we have other ones such as: READ_UNCOMMITTED, REPEATABLE_READ and SERIALIZABLE.

  • READ_COMMITTED will only read committed operations to the database and maintain us safe from dirty reads.
  • READ_UNCOMMITTED allows the current transaction to read the uncommitted changes from another transaction. The lowest isolation level.
  • REPEATABLE_READ prevent dirty reads and if one row is read more than one time in a single transaction, the read result will always be the same.
  • SERIALIZABLE prevents non-repeatable reads, dirty reads, and phantom reads. Has impact on performance.

 

We have 2 choices here:

  • Use the same transaction as the parent method.
  • Create a new connection and run a new transaction.

If we need the inner method running in the same transaction, we can mark it with REQUIRED propagation level. This is the default value:

 

What does it mean?

  • If a transactional context exists, use the same one.
  • If there’s no transactional context, create a new one

 

But if we need to run the inner method in a separate transaction, we must use the REQUIRES_NEW propagation level:

What happens here?

  • If a transaction is running, it will be suspended, create a new transactional context, and once is successfully executed, come back to the caller context (if exists).

Each transaction opens a new connection to the database, so has direct impact on performance if you are not managing your isolation / propagation levels properly depending on your use case. For example, you don’t need Propagation.REQUIRES_NEW to perform read operations.

There are 5 more propagation levels:

  • SUPPORTS
  • MANDATORY
  • NEVER
  • NOT_SUPPORTED
  • NESTED

 

We have seen that if a single process inside the transaction fails, then the entire transaction fails. How is this possible? Simple, Spring handles all RuntimeExceptions thrown by a transactional context. But what if we don’t want to roll back in some cases?

We can add specific configuration to allow rollback or non-rollback depending on the exception thrown:

 

Hope this article helped you and you enjoyed it and had fun reading it.😊

Happy Learning!

Comments