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