Differences between @Transactional and TransactionTemplate in SpringBoot
1. Overview
In this article, we are going to be looking at the difference between the @Transactional annotation and TransactionTemplate in
SpringBoot and which one to use in certain circumstances.
A database transaction enables us to safely execute multiple SQL statements with the capability to roll back ALL changes in case any of failure.
For instance, in a banking application, we will wrap the process to change the balance in the accounts table and update the transactions table in a
database transaction. Such that, if the transaction update fails, the balance change will be reverted.
2. Transaction Scope
One of the main differences between @Transactional and TransactionTemplate is their scope of coverage.
The @Transactional annotation is always added to a method, and thus it covers ALL database UPDATE/INSERT operations within that method.
Listing 2.1 UserService.java
|
|
If an exception were to be thrown while saving the AuditTrail record, the User and UserNotificationPreference records would be rolled back.
TransactionTemplate on the other hand, is a utility class that accepts an implementation of the TransactionCallback interface for execution.
This means ALL the UPDATE/INSERT operations taking place in an implementation of a TransactionCallback will be wrapped in a database operation.
It goes without saying, that database operations outside the callback implementation will not be covered in a transaction.
Listing 2.2 UserService.java
|
|
If an exception were to arise while saving the AuditTrail record, the User and UserNotificationPreference records will still
exist in the database and will not be rolled back.
This is because the saving of the AuditTrail is outside the scope of the transactionTemplate.execute statement.
3. Point of Execution
SQL statements generated in a method annotated with @Transactional are run in the underlying Database at the end of the method or when it returns.
Whereas TransactionTemplate executes its own SQL statements immediately.
This difference between these points of executions makes a world of difference. We will use the methods created in Listing 2.1 and 2.2 above to illustrate this difference.
Listing 3.1 UserServiceUnitTest.java
|
|
From the snippet above, we attempted to create two User records with the same email. This of course will trigger an exception because
the email field is unique.
Despite the exception, emailService.sendWelcomeEmail(user); was still invoked twice - one for each attempt.
This is because the actual saving of the record in the database happens at the end of the method, by which time the email has been sent out already.
As expected, the second attempt will lead to a database exception and rollback the saved record, thereby creating a situation where the User will get a welcome email but their record will not exist in our database.
Let’s observe the same logic but with the help of a TransactionTemplate.
Listing 3.2 UserServiceUnitTest.java
|
|
From the test scenario above, we can see that the second attempt to create a User record with the same email failed as expected but
the emailService is called only once.
This means, the second user will not get a welcome email and their record will not exist in our database.
Similar scenarios to the above abound in day-to-day Engineering, this is why we need to understand the difference and consciously make the right choice.
4. Tips and Tricks
One of the gotchas to look out for while using @Transactional is throwing an exception in the annotated method.
From Listing 2.1 above, if emailService.sendWelcomeEmail(user); throws an uncaught runtime exception,
the created User record will be rolled back.
This is despite that the sending of a welcome email is the last action in that method.
Listing 4.1 UserServiceUnitTest.java
|
|
However, if we are to use the TransactionTemplate version, the already created record will still exist in the database.
Listing 4.2 UserServiceUnitTest.java
|
|
These types of differences do lead to sometimes obscure bugs in the codebase, especially in complex business logic that involves multiple levels of method calls.
Note that there’s nothing wrong with the first behaviour if that is what we want. Also, if emailService.sendWelcomeEmail(user);
is an async method, an exception in it will not lead to a rollback of the transaction as we observed in Listing 4.1
5. Conclusion
Through different examples, we have looked at the difference between @Transactional and TransactionTemplate when it comes to
supporting database transactions in a Spring Boot application and common pitfalls to avoid.
The complete source code for the examples in this article is on GitHub.
Happy Coding