Spring's transaction management with the @Transactional annotation
- 4.4/5
- 5467
- Jul 20, 2024
What is a database transaction?
A database transaction is any operation that is treated as a single unit of work that either completes fully or does not complete at all and leaves the storage system in a consistent state.
Transactions can impact a single record or multiple records.
A.C.I.D. properties
ACID is an acronym that stands for atomicity, consistency, isolation, and durability.
ACID properties ensure that a database transaction (a set of read, write, update, or delete operations) leaves the database in a consistent state even in the event of unexpected errors.
1) Atomicity
Atomicity guarantees that all the commands in a transaction (to read, write, update, or delete data) are treated as a single unit and either succeed or fail together.
The transaction would have either completed successfully or been rolled back if any part of it failed.
2) Consistency
Consistency guarantees that the data is in a consistent state when a transaction starts and when it ends.
If the data gets into an illegal state, the whole transaction fails.
3) Isolation
Isolation ensures that the intermediate state of a transaction is invisible to other transactions.
As a result, transactions that run concurrently don't interfere with each other.
4) Durability
Durability guarantees that once the transaction commit and changes are written to the database, they will persist even in the case of system failures like crashes or power outages.
As a result, the transaction's modifications are never undone.
Spring Transaction Management
Spring transaction management simply means: How does Spring start, commit, or rollback JDBC transactions.
Spring provides both declarative (@Transactional) and programmatic (using a TransactionTemplate or PlatformTransactionManager) transaction management, and developers can choose based on the requirement.
Declarative transaction management is easier and more suitable in most cases, but in some cases, you want fine-grain control, and you can use programmatic transaction management.
In order to support transactions in a spring project, you need to add @EnableTransactionManagement to a @Configuration class.
However, if we're using a Spring Boot project and have spring-data-* or spring-tx dependencies on the classpath, then transaction management will be enabled by default.
The @Transactional Annotation
Any bean or public method annotated with the @Transactional annotation makes sure that the methods will be executed inside a database transaction.
The @Transactional annotation should be used in the service layer because it is this layer's responsibility to define the transaction boundaries.
The modern Spring approach to transaction management is typically as follows:
That's it! No configuration, no XML, and no code are needed.
How does the @Transactional annotation work ?
Spring creates dynamic proxies for classes that declare @Transactional on the class itself or on methods.
It does that through a method called proxy-through-subclassing with the help of the Cglib library.
It is also worth noting that the proxy itself does not handle these transactional states (open, commit, close); the proxy delegated this work to a transaction manager.
The proxy has access to a transaction manager and can ask it to open and close transactions and connections.
Spring offers a PlatformTransactionManager (extends TransactionManager) interface, which, by default, comes with a couple of handy implementations. One of them is the DataSourceTransactionManager.
Pitfalls
As Spring wraps the bean in the proxy, only calls from "outside" the bean are intercepted. That means, any self-invocation calls will not start any transaction, even if the method has the @Transactional annotation.
Moreover, only public methods should be annotated with @Transactional. Methods of any other visibility will silently ignore the annotation, as these are not proxying.
The same is true for other annotations, such as @Cacheable.
Transaction Rollback
By default, only RuntimeException and Error trigger a rollback. A checked exception does not trigger a rollback of the transaction.
The @Transactional annotation, on the other hand, supports rollbackFor or rollbackForClassName attributes for rolling back transactions, as well as noRollbackFor or noRollbackForClassName attributes for avoiding rollback.
The @Transactional annotation attributes
The @Transactional annotation provides the following attributes:
1) Propagation
The "propagation" attribute defines how the transaction boundaries propagate to other methods that will be called either directly or indirectly from within the annotated block.
There are a variety of propagation modes that can be plugged into the @Transactional method.
Propagation | Meaning |
---|---|
REQUIRED | This is the default propagation. In this case, if no active transaction is found, spring creates one. Otherwise, the method appends to the currently active transaction: |
SUPPORTS | If a transaction exists, then the method uses this existing transaction. If there isn't a transaction, it is executed non-transactional. |
MANDATORY | If there is an active transaction, then it will be used. If there isn't an active transaction, then Spring throws an exception. |
REQUIRES_NEW | Spring suspends the current transaction if it exists and then creates a new one. |
NOT_SUPPORTED | If a current transaction exists, first Spring suspends it, and then the method runs without a transaction. |
NEVER | Spring throws an exception if there's an active transaction. |
NESTED | Spring checks if a transaction exists, and if so, it marks a save point. If method execution throws an exception, then the transaction rolls back to this save point. |
2) ReadOnly
The "readOnly" attribute defines if the current transaction is read-only or read-write.
It's a good practise to set the readOnly attribute to true at the class level and override it on a per-method basis for the methods that need to write to the database.
3) RollbackFor and RollbackForClassName
The "rollbackFor" and "rollbackForClassName" attributes define one or more Throwable classes for which the current transaction will be rolled back.
By default, only RuntimeException and Error trigger a rollback. A checked exception does not trigger a rollback of the transaction.
4) NoRollbackFor and NoRollbackForClassName
The "noRollbackFor" and "noRollbackForClassName" define one or more Throwable classes for which the current transaction will not be rolled back.
5) Isolation
The "isolation" attribute describes how changes applied by concurrent transactions are visible to each other.
Source Code: GitHub