@Transactional annotation simplified. At last.
Handling database transactions can be hard and confusing, especially when there are multiple frameworks - each one with its own default configurations.
In this short article, we will discuss key concepts of the @Transactional annotation for you to better understand how things roll under the hood.
TL;DR
For the impatient developer.
- You can do everything the @Tranactional annotation does by yourself but it will cost you a lot of boilerplate code.
- The @Transactional annotation is responsible for defining and handling the DB connection and exceptions for your classes.
- @Transactional annotation defines 2 main things:
a. A transaction flow that either happens entirely or rolls back.
b. Transaction properties (propagation/ isolation/ Timeout/ readOnly/ Rollback). - @Transactional annotation belongs on the service level.
- When using Spring Data JPA, you get enableDefaultTransactions=true for each repository use.
TS;WM (Too Short; Want More) 👇
Can you do without the @Transactional annotation?
yes, you can do everything the @Tranactional annotation does by yourself but it will cost you a lot of boilerplate code. The annotation is responsible for defining and handling DB connections and exceptions for your classes. That means you'll need to handle connections and exceptions on your own.
consider this piece of code:
import java.sql.Connection;
Connection connection = dataSource.getConnection();
try (connection) {
connection.setAutoCommit(false);
// do some sql work
connection.commit();
} catch (SQLException e) {
connection.rollback();
}
When using the @Transactional annotation, Spring takes care of handling each transaction connection and exceptions.
Spring can't really rewrite your code to insert these connection/exceptions changes, but it has a trick up its sleeve, the IoC container. Whenever you'll use the @Transactional annotation on your @Service, Spring will instantiate 2 objects. Your service and a proxy of that service.
The proxy has one job which is to open and close a DB transaction. in turn, it will delegate the connection to the real service.
when debugging your code you can see all that in action. (this example uses Spring Data JPA)
Notice: @Transactional is inherited. keep that in mind when designing your classes.
What can we define with the @Transactional annotation?
The @Transactional annotation is used to define 2 main things.
- A transaction flow that either happens entirely or rolls back.
That means, that you are guaranteed the consistency of your transactions. If a transaction fails somewhere down the line the whole process rolls back to bring your DB to the previous state. - Transaction properties
a. Propagation - define transaction propagation type. (propagation = Propagation.REQUIRED) is set to all transactions by default.
b. Isolation - defines the data contract between transactions.
c. Timeout - sets a timeout for the transaction.
d. readOnly - is the transaction allows to read/write.
e. Rollback - defines the rollback rules.
Where the @Transactional annotation belongs?
It is considered a best practice to put @Transactional on the service level. The service is the one who 'knows' about the relationships between the other services and it will allow you to use multiple objects in a single transaction.
Im using Spring Data JPA do I still need to annotate my services with @Transactional?
Well, when using Spring Data JPA you will get enableDefaultTransactions=true by default. This property sets @Transactional(readOnly=true) for each one of your repositories. so the short answer here is if you need to write in addition to reading from the DB so yes, you'll need the annotation.
Conclusion
In this oversimplified article, we broke down and discussed some of the key concepts of the @Transactional annotation. You can find more information on the Spring official documentation.