Spring Transactions in Java
Introduction
In enterprise applications, operations rarely involve a single database query. Often, multiple steps must succeed or fail as a single, indivisible unit to maintain data integrity. This requires strict adherence to ACID properties (Atomicity, Consistency, Isolation, Durability).
Spring Transactions abstracts away the low-level boilerplate of JDBC, Hibernate, or JPA transaction management, allowing developers to manage transactions cleanly and declaratively using the @Transactional annotation.
How It Works Under the Hood: AOP Proxies
When you annotate a class or method with @Transactional, Spring utilizes Aspect-Oriented Programming (AOP).
Instead of injecting your actual class, Spring injects a Proxy object. When a method is called on this proxy:
- The proxy intercepts the call.
- The proxy opens a database transaction.
- It delegates the call to your actual target method.
- If the method completes successfully, the proxy commits the transaction.
- If the method throws a RuntimeException, the proxy rolls back the transaction.
The Self-Invocation Pitfall
Because Spring uses proxies, calling a @Transactional method from within the same class bypasses the proxy. The transaction will not be started. This is one of the most common mistakes made by developers.
Code Examples
1. Basic Declarative Transactions
The most common usage is applying @Transactional at the Service layer.
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BankService {
private final AccountRepository accountRepository;
public BankService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Transactional
public void transferMoney(Long fromId, Long toId, double amount) {
Account fromAccount = accountRepository.findById(fromId).orElseThrow();
Account toAccount = accountRepository.findById(toId).orElseThrow();
fromAccount.setBalance(fromAccount.getBalance() - amount);
toAccount.setBalance(toAccount.getBalance() + amount);
// Both saves will be committed together.
// If an exception occurs, the entire transaction rolls back.
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
}
2. Customizing Rollback Rules
By default, Spring only rolls back on RuntimeException and Error. Checked exceptions (like IOException) do not trigger a rollback by default. You can change this behavior.
@Transactional(rollbackFor = {CustomCheckedException.class, Exception.class})
public void processOrder(Order order) throws CustomCheckedException {
// If CustomCheckedException is thrown, the transaction will now roll back.
}
Advanced Concepts
1. Transaction Propagation
Propagation defines how transactions relate to each other when one transactional method calls another.
REQUIRED(Default): If a transaction exists, join it. Otherwise, create a new one.REQUIRES_NEW: Suspend the current transaction (if any) and always create a completely new, independent transaction.MANDATORY: Throw an exception if there is no existing transaction.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditLog(String action) {
// This will commit independently of the parent transaction's success or failure.
logRepository.save(new AuditLog(action));
}
2. Transaction Isolation
Isolation levels solve concurrency issues like dirty reads, non-repeatable reads, and phantom reads.
READ_UNCOMMITTED: Fastest, but allows dirty reads (reading uncommitted changes from other transactions).READ_COMMITTED(Usually default): Prevents dirty reads.REPEATABLE_READ: Prevents dirty and non-repeatable reads.SERIALIZABLE: Highest isolation, completely locks accessed data, but significantly impacts performance.
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void generateMonthlyReport() {
// Logic that requires data consistency throughout execution
}
Summary
- Declarative Management: Use
@Transactionalto manage database boundaries elegantly. - AOP Mechanics: Be aware of the proxy-based nature of Spring AOP to avoid the self-invocation bug.
- Customization: Understand how to configure propagation, isolation, and rollback rules to handle complex enterprise workflows correctly.