Understanding Transactional annotation in Spring

1. Introduction

Spring provides support for both programmatic and declarative transactions –

1.1 Programmatic Transactions

With programmatic transactions, transaction management code needs to be explicitly written so as to commit when everything is successful and rolling back if anything goes wrong. The transaction management code is tightly bound to the business logic in this case.


1.2 Declarative Transactions

Declarative transactions separates transaction management code from the business logic. Spring supports declarative transactions using transaction advice (using AOP) via XML configuration in the spring context or with @Transactional annotation.

In this tutorial, we will particularly be concerned about the @Transactional annotation and how it works.

2. Implementation

To start using @Transactional annotation in a Spring based application, we need to first enable annotations in our Spring application by adding the needed configuration into spring context file –

Next is to define the transaction manager bean, with the same name as specified in the above transaction-manager attribute value.

The transaction managers could be –

2.1 DataSource Transaction manager

2.2 Hibernate Transaction manager

2.3 JPA Transaction manager

We are now ready to use @Transactional annotation either at the class or method level.

3. Related Links

4. Understanding @Transactional annotation

At a high level, when a class declares @Transactional on itself or its members, Spring creates a proxy that implements the same interface(s) as the class you’re annotating. In other words, Spring wraps the bean in the proxy and the bean itself has no knowledge of it. A proxy provides a way for Spring to inject behaviors before, after, or around method calls into the object being proxied.

Internally, its the same as using a transaction advice (using AOP), where a proxy is created first and is invoked before/after the target bean’s method.

The generated proxy object is supplied with a TransactionInterceptor, which is created by Spring. So when the @Transactional method is called from client code, the TransactionInterceptor gets invoked first from the proxy object, which begins the transaction and eventually invokes the method on the target bean. When the invocation finishes, the TransactionInterceptor commits/rolls back the transaction accordingly.

Note that only calls from “outside” the target bean go through the proxy.

5. Understanding Propagate and readOnly annotation attributes

5.1 Transaction readOnly

If you don’t explicitly set readOnly attribute to true, you will have read/write transactions.

Its always better to explicitly specify the readOnly attribute, as we have noticed some massive performance improvements with Hibernate because of this.

5.2 Transaction propagation

Transaction propagation is REQUIRED by default, which means that the same transaction will propagate from a transactional caller to transactional callee. It will create a new transaction or reuse the one if available. For example, if a read-only transaction calls a read-write transaction method, the whole transaction will be read-only.

Depending on the transaction propagation attribute (like for REQUIRES_NEW), sometimes the existing transaction is suspended/paused at some point, a new one is always started and eventually committed, and after that the first transaction is resumed.

5.3 Isolation Level

Isolation level defines a contract between transactions.

  • Read Uncommitted – Allows dirty reads, when a transaction is not yet committed by a thread and another thread is reading the dirty data.
  • Read Committed – Does not allow dirty reads. Only lets a thread to read values which have already been committed by other running transactions in another threads.
  • Repeatable Read – If the same data is read twice in the same transaction, it will always be the same. This level guarantees that any data once read cannot change.
  • Serializable – Transactions occur with locking at all levels (read, range and write locking), because of which they are executed in a fixed sequence. It doesn’t allow concurrent transactions and leads to a performance hit.

With the Repeatable Read isolation level, the state of the database is maintained from the start of the transaction. If you retrieve a value in session1, then update that value in session2, retrieving it again in session1 will return the same results.

6. Source code download

Receive our updates to your inbox

Get more stuff like this
in your inbox

Subscribe to our mailing list and get interesting stuff and updates to your email inbox.