Transaction Propagation Behaviors and Programmatic Transactions in Spring @Transactional
Tarnsaction Propagation Behaviors in @Transactional
Propagation Behaviors
@Transactional(propagation = Propagation.REQUIRED) // Join existing transaction or create new one (default)
@Transactional(propagation = Propagation.NOT_SUPPORTED) // Execute without transaction
@Transactional(propagation = Propagation.REQUIRES_NEW) // Always create new transaction, suspending existing one
@Transactional(propagation = Propagation.MANDATORY) // Must execute within existing transaction, throws exception otherwise
@Transactional(propagation = Propagation.NEVER) // Must execute without transaction, throws exception otherwise
@Transactional(propagation = Propagation.SUPPORTS) // Use transaction if caller has one, otherwise execute without
@Transactional(propagation = Propagation.NESTED) // Execute nested transaction if exists, otherwise create new
Isolation Levels
@Transactional(isolation = Isolation.READ_UNCOMMITTED) // Read uncommitted data (dirty reads, non-repeatable reads)
@Transactional(isolation = Isolation.READ_COMMITTED) // Read committed data (non-repeatable reads, phantom reads)
@Transactional(isolation = Isolation.REPEATABLE_READ) // Repeatable reads (phantom reads)
@Transactional(isolation = Isolation.SERIALIZABLE) // Serializable isolation
Database Defaults:
- MySQL: REPEATABLE_READ
- SQL Server: READ_COMMITTED
- Oracle: Supports READ_COMMITTED and SERIALIZABLE (no dirty reads)
Transaction Isolation Concepts
- Dirty Read: Reading uncommitted data from another transaction
- Non-repeatable Read: Reading different results for same data within same transaction due to committed updates from other transactions
- Repeatable Read: Consistent reads within same transaction, unaffected by other transactions' committed updates
- Phantom Read: Reading newly inserted data committed by another transaction
@Transactional Annotation Attributes
// Read-only transaction
@Transactional(readOnly = true) // Default: false
// Transaction timeout
@Transactional(timeout = 30) // Default: -1 (no timeout)
// Rollback rules
@Transactional(rollbackFor = RuntimeException.class) // Single exception
@Transactional(rollbackFor = {RuntimeException.class, Exception.class}) // Multiple exceptions
// No-rollback rules
@Transactional(noRollbackFor = RuntimeException.class) // Single exception
@Transactional(noRollbackFor = {RuntimeException.class, Exception.class}) // Multiple exceptions
Common @Transactional Pitfalls
- Method must be public
- Only unchecked exceptions trigger rollback by default
- Database engine must support transactions (e.g., MySQL InnoDB)
- Spring must scan the package containing the class
- Avoid calling
this.method()within same class (bypasses proxy)
Programmatic Transaction Management
Approach 1: Using PlatformTransactionManager
@Autowired
private PlatformTransactionManager txManager;
public void executeWithTransaction() {
TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
try {
for (int counter = 0; counter < 10; counter++) {
dataService.saveRecord(counter);
if (counter == 5) {
throw new RuntimeException("Transaction test exception");
}
}
txManager.commit(status);
} catch (Exception error) {
txManager.rollback(status);
throw error;
}
}
Approach 2: Using TransactionTemplate
@Resource
private TransactionTemplate transactionTemplate;
public void executeWithTemplate() {
transactionTemplate.execute(status -> {
try {
dataService.saveRecord(1);
dataService.saveRecord(2);
} catch (Exception error) {
status.setRollbackOnly();
return false;
}
return true;
});
}
Note: Programmatic transactions can be combined with @Transactional annotations and both will work correctly.