@Transactional annotation is the metadata used for managing transactions in the Spring Boot application. To configure Spring Transaction, this annotation can be applied at the class level or method level. In an enterprise application, a transaction is a sequence of actions performed by the application that together pipelined to perform a single operation. For example, booking a flight ticket is also a transaction where the end user has to enter his information and then make a payment to book the ticket.
Why Do We Need Transaction Management?
Let’s understand transactions with the above example, if a user has entered his information the user’s information gets stored in the user_info table. Now, to book a ticket he makes an online payment and due to some reason(system failure) the payment has been canceled so, the ticket is not booked for him. But, the problem is that his information gets stored on the user_info table. On a large scale, more than thousands of these things happen within a single day. So, it is not good practice to store a single action of the transaction(Here, only user info is stored not the payment info).
To overcome these problems, spring provides transaction management, which uses annotation to handle these issues. In such a scenario, spring stores the user information in temporary memory and then checks for payment information if the payment is successful then it will complete the transaction otherwise it will roll back the transaction and the user information will not get stored in the database.
@Transactional Annotation
In Spring Boot, @Transactional annotation is used to manage transactions in a Spring boot application and used to define a scope of transaction. This annotation can be applied to the class level or method level. It provides data reliability and consistency. When a method is indicated with @Transactional annotation, it indicates that the particular method should be executed within the context of that transaction. If the transaction becomes successful then the changes made to the database are committed, if any transaction fails, all the changes made to that particular transaction can be rollback and it will ensure that the database remains in a consistent state.
Note: To use @Transactional annotation, you need to configure transaction management by using @EnableTransactionManagement to your main class of Spring Boot application.
Configure Transaction in Spring Boot
In this example, we will create an application to store user information along with his address information and will use spring transaction management to resolve the transaction break problem.
The @Transactional annotation allows you to define transaction boundaries declaratively. For those looking to deepen their understanding of Spring Boot’s features, the Java Backend course covers essential security practices for safeguarding your applications.
Step By Step Implementation of Transaction Management
Step 1: Create A Spring Boot Project
In this step, we will create a spring boot project. For this, we will be using Spring Initializr.
Step 2: Add Dependencies
We will add the required dependencies for our spring boot application.
Step 3: Configure Database
Now, we will configure the database in our application. We will be using the following configurations and add them to our application.properties file.
Step 4: Create Model Class
In this step, we will create our model class. Here, we will be creating two model classes, Employee and Address. While creating the model class we will be using Lombok Library.
Employee.java
Address.java
Step 5: Create a Database Layer
In this step, we will create a database layer. For this, we will be creating EmployeeRepository and AddressRepository and will be extending JpaRepository<T, ID> for performing database-related queries.
EmployeeRepository.java
AddressRepository.java
Step 6: Create a Service Layer
You can use @Transactional annotation in service layer which will result interacting with the database. In this step, we will create a service layer for our application and add business logic to it. For this, we will be creating two classes EmployeeService and AddressService. In EmployeeService class we are throwing an exception.
EmployeeService.java
AddressService.java
Step 7: Create Controller
In this step, we will create a controller for our application. For this, we will create a Controller class and add all the mappings to it.
Controller.java
Step 8: Running Our Application
In this step, we will run our application. Once, we run our application using hibernate mapping in our database required tables will be created.
As we can see in logs, our table has been created. We can also confirm it by looking at our database.
Now, we will request our application for adding an employee to it, using postman. To learn more about postman please refer to Postman – Working, HTTP Request & Responses. Once, we hit the request, the request moves from the controller to the service layer where our business logic is present.
As we can see in the above response we have added an employee. We can also check our database for employee data and address data.
Similarly, we can also check for address data.
Step 9: Problem Without Transaction Management
In the EmployeeService class, we initialize the address object to NULL. Consequently, the employee’s details cannot be stored in the database due to the null address object. However, as we are not employing transaction management, the employee basic information will persisted in the database. The address details are omitted because of the null value.
Note: Applying the @Transactional annotation to a method will not trigger a rollback of any operation if @EnableTransactionManagement is not used.
Now, we will delete our table from the database and again run our application and will request the application to create an employee.
we have initialized the address object as null and requested the application, we have an employee created in the database but the address information is not, as we have received a null pointer exception. But, this is not good practice in transaction management, as employees should be saved only when the address is saved and vice-versa.
Step 10: Transaction Management
To overcome this problem, we will use @Transactional annotation. This will ensure that the transaction should be complete. That is, either both employee and address data should be stored or nothing will get stored. For using transaction management, we need to use @EnableTransactionManagement in the main class of our spring boot application and also, and we need to annotate our addEmployee() method in EmployeeService class with @Transactional annotation.
TransactionManagementApplication.java
Step 11: Running Application
Now, we have enabled transaction management for our application. We will again delete the tables from our database and request our application to add an employee.
As we can see in the above media file, this time the employee data do not get stored in the database, nor did the address data. This way the spring has handled the transaction that both employees and address data gets stored or no data gets stored.
Conclusion
In this article, we have learned basic configuration of transaction management using in a Spring Boot application. Also we have covered @Transactional and @EnableTransactionManagement annotation and it’s uses with a step by step implementation in a spring boot application.
Best Practices of using@Transactional in Spring Boot
What is the difference between @Transactional and @Transactional(propagation = Propagation.REQUIRES_NEW)?
@Transactional creates a transaction if none exists or joins an existing transaction if one is already active.
Where as
@Transactional(propagation = Propagation.REQUIRES_NEW) creates a new transaction, suspending the current transaction (if one exists).
methodA() is marked with @Transactional and calls methodB(), which is marked with @Transactional(propagation = Propagation.REQUIRES_NEW). When methodA() is invoked, Spring creates a transaction and propagates it to methodB(). However, because methodB() has a REQUIRES_NEW propagation setting, it creates a new transaction, suspending the current transaction in methodA().
What happens if a @Transactional method calls another @Transactional method?
By default, Spring uses a “proxy-based” approach to manage transactions. If a @Transactional method calls another @Transactional method within the same class, the call is made to the original instance (not the proxy) and the transactional behavior is not applied.
How does Spring handle transactions when calling a method on a different bean?
When calling a method on a different bean, Spring creates a new proxy around the target bean, which allows it to manage the transactional behavior. The behavior of the transaction is determined by the propagation settings of the @Transactional annotation on the calling method.
methodA() calls methodB() on a different bean (OtherService). When methodB() is invoked, Spring creates a new proxy around the target bean and applies the transactional behavior based on the propagation settings of the calling method (methodA()).
What happens when a method marked with @Transactional throws an unchecked exception?
When a method marked with @Transactional throws an unchecked exception, Spring will automatically roll back the transaction by default. This behavior ensures that data changes made in a transaction will not be persisted in the database if an error occurs.
In the above example, the updateUser() method is marked with @Transactional and updates the email address of a user in the database. However, the method also throws an unchecked exception after saving the changes to the database. Because the exception is unchecked, Spring will roll back the transaction and discard the changes made to the user’s email address.
If you want to change the default behavior and allow the transaction to commit even if an unchecked exception occurs, you can add the noRollbackFor attribute to the @Transactional annotation:
We’re telling Spring not to roll back the transaction if a RuntimeException occurs. This can be useful in some cases where you want to keep the changes made within the transaction even if an error occurs.
What is the default rollback behavior of a @Transactional method?
@Transactional method will roll back the transaction on any unchecked exception. You can customize this behavior using the rollbackFor or noRollbackFor properties of the @Transactional annotation.
methodA() is marked with @Transactional(rollbackFor = {Exception.class}). This means that the transaction will be rolled back if any exception of type Exception (or its subclasses) is thrown. If an exception of a different type is thrown, the transaction will not be rolled back.
Can you use @Transactional on private methods?
No, @Transactional only works on public methods. Spring creates a proxy around the public methods of a bean to manage the transactional behavior. Private methods are not visible to the proxy and therefore cannot be wrapped in a transactional context.
If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ if you need to annotate non-public methods.
How does the @Transactional annotation handle concurrency issues?
Concurrency issues can occur when multiple threads access the same data simultaneously, leading to inconsistencies and data corruption. Spring Boot’s @Transactional annotation provides a mechanism for handling concurrency issues by serializing transactions that modify the same data, preventing multiple threads from modifying the same data at the same time.
NOTE ** The default isolation level used by the @Transactional annotation in Spring is Isolation.DEFAULT. This means that Spring will use the default isolation level of the underlying data source, which is typically “read committed” for most databases. This default behavior can prevent most concurrency issues by ensuring that transactions do not interfere with each other.
The updateUser() method is marked with @Transactional and updates a user’s email address in the database. When multiple threads attempt to modify the same user’s email address at the same time, Spring will ensure that only one thread can modify the data at any given time by serializing the transactions.