Skip to main content

Spring's transaction management with the @Transactional annotation

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.

                                                                                               
PropagationMeaning
REQUIREDThis 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:
SUPPORTSIf a transaction exists, then the method uses this existing transaction. If there isn't a transaction, it is executed non-transactional.
MANDATORYIf there is an active transaction, then it will be used. If there isn't an active transaction, then Spring throws an exception.
REQUIRES_NEWSpring suspends the current transaction if it exists and then creates a new one.
NOT_SUPPORTEDIf a current transaction exists, first Spring suspends it, and then the method runs without a transaction.
NEVERSpring throws an exception if there's an active transaction.
NESTEDSpring 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

Comments

Popular posts from this blog

Deploying Spring Boot microservices on Kubernetes Cluster

This article guides you through the deployment of two Spring Boot microservices, namely "order-service" and "inventory-service," on Kubernetes using "MiniKube" . We will establish communication between them, with "order-service" making calls to an endpoint in "inventory-service." Additionally, we will configure "order-service" to be accessible from the local machine's browser . 1) Create Spring Boot microservices The Spring Boot microservices, "order-service" and "inventory-service," have been developed and can be found in this GitHub repository. If you are interested in learning more about creating Spring Boot REST microservices, please refer to this or this (Reactive) link. 2) Build Docker Images The Docker images for both "order-service" and "inventory-service" have already been generated and deployed on DockerHub, as shown below. codeburps/order-service cod...

Circuit Breaker Pattern with Resilience4J in a Spring Boot Application

Read Also: Spring Cloud Circuit Breaker + Resilience4j Resilience4j is a lightweight fault tolerance library that draws inspiration from Netflix Hystrix but is specifically crafted for functional programming. The library offers higher-order functions, known as decorators , designed to augment any functional interface, lambda expression, or method reference with features such as Circuit Breaker, Rate Limiter, Retry, or Bulkhead . These functionalities can be seamlessly integrated within a project, class, or even applied to a single method. It's possible to layer multiple decorators on any functional interface, lambda expression, or method reference, allowing for versatile and customizable fault tolerance. While numerous annotation-based implementations exist online, this article focuses solely on the reactive approach using router predicates and router functions . How Circuit Breaker Pattern works? In general, a circuit breaker functions as an automatic electrical s...

Reactive programming in Java with Project Reactor

Reactive programming is a declarative programming paradigm that focuses on building applications that are responsive, resilient, and scalable in the face of modern challenges like concurrency, distributed systems, and asynchronous data streams . Reactive programming provides a set of tools, patterns, and abstractions to handle asynchronous and event-driven programming more effectively. Imperative programming focuses on describing the step-by-step instructions or commands that the computer needs to follow to achieve a specific task. In this paradigm, you explicitly state how to perform each operation and control flow in your code. The emphasis is on "how" the computation should be done. int sum = 0; for (int i = 1; i Declarative programming emphasizes specifying what you want to achieve rather than detailing how to achieve it. You describe the desired outcome or the properties of the result, and the programming language or framework handles the execution detai...