Connections and Transactions — Spring Boot Hibernate All about @Transactional

Kubra Ozer
6 min readJan 18, 2021

Introduction

https://aishwaryavaishno.wordpress.com/2013/06/07/hibernate-persistent-context-and-objects-lifecycle/

With this article, you are going to learn about transactions in spring boot application and how to use @Transactional annotation with declarative way.

I will follow the Spring Boot Persistence Best Practices book steps that is written by Anghel Leonard. And I will try to summarize and simplify the items of the specific section in this book.

Firstly, you need to know what is transaction in a database. Transaction is a logical unit that is independently executed for data retrieval or updates. So we should control programmatically transaction management and database connections. But Spring provide us a declarative way and handle this boilerplate code for us so that we can focus on our business logic.

try { 
utx.begin();
businessLogic();
utx.commit();
} catch(Exception ex) {
utx.rollback();
throw ex;
}
//There is no need this with @Transactional annotation

When we should get database connection Acquisition ?

Hibernate will acquire the database connection of a JDBC transaction right after the transaction starts. This can be performance penalty if we acquire a connection where there is no need to start database connection. So , firstly we should determine which place is about database connection and which place is about other business logic.

In Spring, a method annotated with @Transactional acquires the database connection immediately after it is called.

When we should use Transactional ?

To be honest before I researched how transaction mechanism works in hibernate , I had only used @Transactional annotation with readOnly property. That is just what I know about @Transactional and also I know that I should know something more :). Probably you know what means of @Transactional(readOnly=true). But how it exactly works ?

  • Basically, If we use @Transactional(readOnly = true) to a method which is performing create or update operation then we will not have newly created or updated record into the database but we will have the response data. So what are the readOnly and readWrite mode?
  • read- only mode: In this mode, the hydrated state is discarded from memory and only the entities are kept in the Persistence Context.
  • read-write mode: In this mode, both the entity and its hydrated state are available in the Persistence Context. They are available during the Persistence Context (until the Persistence Context is closed) or until the entity is detached. Hibernate extracts the loaded state from the underlying JDBC ResultSet. It is called hydration. Hydrated and detached state is needed by dirty checking mechanism and optimistic locking mechanism to compare current entity state and loaded snapshot for update statements. To be simple in this article , I won’t detail this mechanisms. But you can look at the following links for details.

(I totally recommend you to follow the Vlad Mihalcea’s blogs to learn how to implement JPA-Hibernate with good performance and how to optimize our existing application which have some performance-penalties.)

Setting @Transactional(readOnly=true) for read-only data is a good optimization of performance since the hydrated/loaded state is discarded. This allows Spring to optimize the underlying data access layer operations.

There are two case which is needed to be careful when implement @Transactional. Sometimes this annotation can be ignored;

  • If you add @Transactional to a private, protected, or package-protected method.
  • If you add @Transactional to a method defined in the same class as where it is invoked.
@Service
public class UserService {
@Autowired
private SampleRepository sampleRepository;

public User createUser(String name) {
User newUser = new User(name);
return this.saveUser(newUser);
// self-invocation, You should call this transactional method from other class.
}
@Transactional
public User saveUser(User newUser) {
sampleRepository.save(newUser);
return newUser;
}
}

By default Spring doesn’t support transactional-context. You need to determine explicitly.

Okay, this example is for simple application and small logic. Be careful when using @Transactional annotation when it comes to your logic is getting complex.

Let’s talk about long-running versus short transactions:

The service-method is annotated with @Transactional(readOnly = true) to explicitly define the transactional-context boundaries.

public void longRunningServiceMethod() 
{
//Service-method start ...

longRunningMethod();
//Complex,long running method for real-life implementation.
SampleEntity entity = sampleRepository.fetchByName("Vilad");
//Service-method done ...
}

So, what is happening here? Spring starts the transaction and acquires the database connection immediately, before starting to run the longRunningMethod() method. The database connection is opened right away and is ready to be used. But we don’t use it right away, we just keep it open! We run some other tasks before calling fetchByName() , which is before the first interaction with the database connection. With this scenerio we open a transaction and occupy the database unnecessarily.

So It seems like we can solve this problem for such a case by moving @Transactional in repository interface.

It’s good practice to define the @Transactional(readOnly = true) annotation at the class level and only override it for read-write methods.

But what if we call more than one query-methods, can we achieve ACID for database operation with above example of repository class? For example , It is possible that after fetching some entities(findAllById) you may want to change some of these entity(deleteByName). In result you have two different transaction context. Calling these two method in same service method without @Transactional annotation , you will lose ACID properties. So you need to use @Transactional at service level.

@Transactional in my experience is most useful if you have a service method with multiple JPA calls, and you want them all to happen in a single DB transaction.

Other alternative is delaying connection acquisition to handle long-running method for performance boosting.

We can delay the connection acquisition via the following these two settings:

spring.datasource.hikari.auto-commit=falsespring.jpa.properties.hibernate.connection.provider_disables_autocommit=truepublic void longRunningServiceMethod() 
{
//Service-method start ...

longRunningMethod();
//Complex,long running method for real-life implementation.
SampleEntity entity = sampleRepository.fetchByName("Vilad");
//Service-method done ...
}

So our longRunningMethod() which is called in the longRunningServiceMethod() is running without keeping a database connection. When it comes to sampleRepository.fetchByName then the connection happens until the end of the method. So you can use @Transactional at service level and keep safe your application for long-running methods and ACID properties.

Prepare your repository interfaces:

This steps is quoted from the book which is referred to in Introduction section:

  • Annotate the repository interfaces with @Transactional(readOnly=true).
  • Override @Transactional(readOnly=true) with @Transactional for query-methods that modify data/generate DML (such as INSERT, UPDATE, and DELETE).
  • For Hibernate 5.2.10+, delay the database connection acquisition until it’s really needed
  • Evaluate each service-method to decide if it should be annotated with @Transactional or not.
  • If you decide to annotate a service-method with @Transactional then add the proper @Transactional. You should add
  • @Transactional(readOnly=true) if you call just read-only query-methods and add @Transactional if you call at least one query-method that can modify data.
  • Be sure to evaluate the transaction duration and behavior in the context of the current transaction propagation mechanism and strive for short and short/fast transactions.

References

--

--