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 does

  • Different 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 TypeDescription
REQUIRED (default)Uses an existing transaction if available; otherwise, creates a new one.
REQUIRES_NEWAlways starts a new transaction, suspending the existing one.
NESTEDRuns inside a nested transaction within an existing one.
SUPPORTSUses an existing transaction if available; otherwise, runs without one.
NOT_SUPPORTEDRuns without a transaction, suspending any existing one.
MANDATORYMust run inside an existing transaction; throws an error if none exists.
NEVERMust 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 LevelDescription
DEFAULTUses the database default isolation level.
READ_UNCOMMITTEDAllows reading uncommitted changes (dirty reads).
READ_COMMITTEDPrevents dirty reads but allows non-repeatable reads.
REPEATABLE_READPrevents dirty and non-repeatable reads but allows phantom reads.
SERIALIZABLEFully 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!