Understanding @Transactional in Spring Boot - A Complete Guide
When working with databases in Spring Boot, ensuring data integrity is crucial. Transactions help maintain consistency, and Spring provides a powerful way to manage them using the @Transactional
annotation.
In this blog, we will explore:
What
@Transactional
doesDifferent ways to use it
Key attributes like propagation, isolation, rollbackFor, readOnly, and timeout
Real-world examples
What is @Transactional
?
@Transactional
is a Spring annotation used to manage transactions declaratively. It ensures that a set of database operations executes as a single unit of work, meaning:
If all operations succeed, changes are committed.
If any operation fails, all changes are rolled back (undoing the previous changes).
This guarantees data integrity and prevents partial updates to the database.
Using @Transactional
in Spring Boot
1. Applying @Transactional
at the Class Level
If you apply @Transactional
at the class level, all methods in the class become transactional by default.
@Service
@Transactional
public class UserService {
public void createUser(User user) {
// This method is transactional
}
public void deleteUser(Long userId) {
// This method is also transactional
}
}
Every method in UserService
now runs inside a transaction unless explicitly overridden.
2. Applying @Transactional
at the Method Level
You can also apply @Transactional
at the method level to make specific methods transactional while others remain unaffected.
@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
// Only this method is transactional
}
}
If both class-level and method-level @Transactional
annotations exist, the method-level annotation takes precedence.
Key Attributes of @Transactional
Spring provides several attributes to control how transactions behave. Let’s break them down:
1. Propagation - How Transactions Interact
Propagation defines how a method behaves when another transaction is already in progress.
Propagation Type | Description |
REQUIRED (default) | Uses an existing transaction if available; otherwise, creates a new one. |
REQUIRES_NEW | Always starts a new transaction, suspending the existing one. |
NESTED | Runs inside a nested transaction within an existing one. |
SUPPORTS | Uses an existing transaction if available; otherwise, runs without one. |
NOT_SUPPORTED | Runs without a transaction, suspending any existing one. |
MANDATORY | Must run inside an existing transaction; throws an error if none exists. |
NEVER | Must not run inside a transaction; throws an error if one exists. |
Example:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createInvoice() {
// Always runs in a new transaction
}
2. Isolation - Controlling Data Visibility
Isolation levels define how changes made by one transaction are visible to others.
Isolation Level | Description |
DEFAULT | Uses the database default isolation level. |
READ_UNCOMMITTED | Allows reading uncommitted changes (dirty reads). |
READ_COMMITTED | Prevents dirty reads but allows non-repeatable reads. |
REPEATABLE_READ | Prevents dirty and non-repeatable reads but allows phantom reads. |
SERIALIZABLE | Fully isolates transactions, preventing all concurrency issues. |
Example:
@Transactional(isolation = Isolation.SERIALIZABLE)
public void processTransaction() {
// Fully isolated transaction
}
3. Rollback Behavior (rollbackFor
, noRollbackFor
)
By default, transactions rollback only for unchecked exceptions (RuntimeException
). To rollback for checked exceptions, use rollbackFor
.
Example:
@Transactional(rollbackFor = SQLException.class)
public void updateUser() throws SQLException {
// Rolls back if a SQLException occurs
}
To prevent rollback for a specific exception, use noRollbackFor
.
@Transactional(noRollbackFor = CustomException.class)
public void processPayment() {
// Will not roll back for CustomException
}
4. Read-Only Transactions (readOnly
)
Use readOnly = true
for methods that only fetch data, optimizing performance.
Example:
@Transactional(readOnly = true)
public List<User> getUsers() {
return userRepository.findAll();
}
5. Transaction Timeout (timeout
)
Define how long a transaction should run before timing out.
Example:
@Transactional(timeout = 5) // Must complete in 5 seconds
public void processPayment() {
// Rolls back if execution exceeds 5 seconds
}
Best Practices for @Transactional
✔ Use readOnly = true
for read-only queries to improve performance.
✔ Be cautious with REQUIRES_NEW
as it always creates a new transaction.
✔ Use rollbackFor
wisely to handle checked exceptions.
✔ Keep transactions short to avoid locking resources for too long.
✔ Avoid applying @Transactional
to private methods since Spring’s AOP mechanism won’t work.
Conclusion
The @Transactional
annotation in Spring Boot is a powerful tool for managing transactions. By understanding propagation, isolation levels, rollback rules, read-only transactions, and timeouts, you can ensure data consistency and performance optimization.
Would you like to see more real-world scenarios or troubleshooting tips? Let me know in the comments!