
Transactional in Spring: Propagation and Isolation Levels
Introduction to the @Transactional Annotation in Spring
The @Transactional annotation is one of the key features of the Spring framework, used to manage transactions declaratively. It allows developers to specify how and when a transaction should be started, committed, or rolled back.
In this tutorial, we will explore in depth the propagation and isolation levels of transactions in Spring, providing practical examples to illustrate each concept.
What is Transaction Propagation?
Transaction propagation refers to how a transaction is handled when a method annotated with @Transactional is called from another method also annotated with @Transactional. Spring provides several propagation options that define the behavior of the transaction in different scenarios.
Types of Propagation
Here are the main types of propagation available in Spring:
- REQUIRED: This is the default behavior. If a transaction is already in progress, the method will execute within that transaction. Otherwise, a new transaction will be created.
- REQUIRES_NEW: Always creates a new transaction, suspending the current transaction if there is one. This option is useful when you need to ensure that an operation is performed regardless of the current transaction state.
- SUPPORTS: If there is an ongoing transaction, the method will execute within it. Otherwise, it will execute without a transaction.
- NOT_SUPPORTED: The method will always execute outside of a transaction, suspending any ongoing transaction.
- MANDATORY: The method must be executed within a transaction. If there is no ongoing transaction, an exception will be thrown.
- NEVER: The method must be executed outside of a transaction. If there is an ongoing transaction, an exception will be thrown.
- NESTED: If a transaction is already in progress, the method will execute within a nested transaction. Otherwise, if there is no transaction, a new one will be created.
Propagation Example
Let's consider a scenario where we have two services: ServiceA and ServiceB. ServiceA calls a method on ServiceB. Here’s how we can configure the propagation:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ServiceA {
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// logic of method A
serviceB.methodB();
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// logic of method B
}
}
In the example above, if methodA is called and a transaction is already in progress, methodB will execute in a new transaction, regardless of the outcome of methodA.
Transaction Isolation Levels
Isolation levels define how transactions interact with each other. They control the visibility of changes made by one transaction to other transactions. Spring supports the following isolation levels:
Types of Isolation
- DEFAULT: Uses the database's default isolation level.
- READ_UNCOMMITTED: Allows a transaction to read uncommitted data from another transaction. This can lead to dirty reads.
- READ_COMMITTED: Ensures that a transaction can only read committed data. It prevents dirty reads but may result in non-repeatable reads.
- REPEATABLE_READ: Ensures that a transaction can read the same data multiple times and get the same result. This prevents non-repeatable reads but can lead to phantom reads.
- SERIALIZABLE: The highest level of isolation, which ensures that transactions are completely isolated from each other. This prevents dirty reads, non-repeatable reads, and phantom reads, but can impact performance.
Isolation Level Example
Now let's add an isolation level to our previous example:
import org.springframework.transaction.annotation.Isolation;
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public void methodB() {
// logic of method B
}
}
In the code above, methodB is configured to use the READ_COMMITTED isolation level, ensuring that only committed data is read.
When to Use Each Type of Propagation and Isolation
Choosing the correct type of propagation and isolation level depends on the context of your application and data consistency requirements.
- REQUIRED: Use when you want methods to execute within the same transaction, ensuring atomicity.
- REQUIRES_NEW: Ideal for operations that should not be affected by a parent transaction, such as audit logs.
- READ_COMMITTED: A good balance between performance and consistency, avoiding dirty reads.
- SERIALIZABLE: Use when data integrity is critical and you can tolerate performance loss.
Conclusion
The @Transactional annotation in Spring is a powerful tool for effectively managing transactions. Understanding propagation and isolation levels is crucial for ensuring data integrity and consistency in your applications.
Try applying different propagation and isolation configurations in your services to see how it affects transaction behavior and the performance of your application.
Call to Action
If you want to deepen your knowledge about Spring and transactions, consider exploring the official documentation of the Spring Framework and practicing with examples in your projects.
FAQ
What happens if an exception occurs in a method annotated with @Transactional?If an unchecked exception occurs, the transaction will be automatically rolled back. For checked exceptions, you must configure the annotation to roll back the transaction.
Can I use @Transactional on private methods?No, the @Transactional annotation must be applied to public methods, as Spring uses proxies to apply transaction logic.
The @Transactional annotation allows for declarative management, making the code cleaner and easier to maintain, while programmatic management requires more code and can be more error-prone.
You can use testing frameworks like JUnit and Spring Test to create tests that verify transaction behavior, ensuring that operations are performed correctly.
What are nested transactions?Nested transactions allow a transaction to contain other transactions. In Spring, this is done using the NESTED propagation, allowing you to roll back only the inner transaction if necessary.

Eridani Melo
Full Stack Developer