Spring Transaction Management: A Comprehensive Guide

Introduction to Spring Transactions

Spring provides a powerful transaction management framework that supports both declarative and programmatic transaction management. This guide covers transaction concepts, configuration, and best practices.

Transaction Basics

Basic Transaction Management


// Service with transaction management
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
    }
    
    @Transactional(readOnly = true)
    public User getUser(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

// Configuration
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
                

Transaction Attributes

Configuring Transaction Behavior


@Service
public class TransactionAttributesService {
    // Propagation behavior
    @Transactional(propagation = Propagation.REQUIRED)
    public void requiredTransaction() {
        // Uses existing transaction or creates new one
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void requiresNewTransaction() {
        // Always creates new transaction
    }
    
    // Isolation level
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void readCommittedTransaction() {
        // Uses READ_COMMITTED isolation level
    }
    
    // Timeout
    @Transactional(timeout = 30)
    public void timeoutTransaction() {
        // Transaction times out after 30 seconds
    }
    
    // Rollback rules
    @Transactional(rollbackFor = {SQLException.class, DataAccessException.class})
    public void rollbackTransaction() {
        // Rolls back on specific exceptions
    }
}
                

Transaction Propagation

Different Propagation Behaviors


@Service
public class TransactionPropagationService {
    @Autowired
    private UserService userService;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void outerTransaction() {
        // Creates new transaction
        userService.innerTransaction();  // Uses same transaction
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void requiresNewTransaction() {
        // Creates new transaction
        userService.innerTransaction();  // Creates new transaction
    }
    
    @Transactional(propagation = Propagation.NESTED)
    public void nestedTransaction() {
        // Creates new transaction
        userService.innerTransaction();  // Creates savepoint
    }
    
    @Transactional(propagation = Propagation.MANDATORY)
    public void mandatoryTransaction() {
        // Requires existing transaction
        userService.innerTransaction();
    }
}
                

Transaction Isolation

Isolation Levels


@Service
public class TransactionIsolationService {
    // Read Uncommitted
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void readUncommitted() {
        // Can read uncommitted changes
    }
    
    // Read Committed
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void readCommitted() {
        // Can only read committed changes
    }
    
    // Repeatable Read
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void repeatableRead() {
        // Consistent reads within transaction
    }
    
    // Serializable
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void serializable() {
        // Highest isolation level
    }
}
                

Programmatic Transactions

Using TransactionTemplate


@Service
public class ProgrammaticTransactionService {
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void executeInTransaction() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                // Transactional code
            }
        });
    }
    
    public User createUserWithTransaction(User user) {
        return transactionTemplate.execute(status -> {
            // Transactional code
            return userRepository.save(user);
        });
    }
}
                

Transaction Events

Handling Transaction Events


@Component
public class TransactionEventListener {
    @TransactionalEventListener
    public void handleTransactionCommitted(TransactionPhase phase) {
        // Handle transaction committed event
    }
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleTransactionRolledBack() {
        // Handle transaction rollback event
    }
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    public void handleTransactionCompleted() {
        // Handle transaction completion event
    }
}
                

Transaction Best Practices

  • Use appropriate propagation behaviors
  • Choose correct isolation levels
  • Set reasonable timeouts
  • Handle exceptions properly
  • Use read-only transactions when possible
  • Keep transactions as short as possible
  • Avoid nested transactions when possible
  • Monitor transaction performance

Common Pitfalls

Issues to Avoid


// 1. Transaction scope issues
@Service
public class TransactionScopeService {
    // Bad: Transaction too broad
    @Transactional
    public void broadTransaction() {
        // Long-running operation
        Thread.sleep(1000);
        // Network call
        // Database operations
    }
    
    // Good: Narrow transaction scope
    @Transactional
    public void narrowTransaction() {
        // Only database operations
    }
}

// 2. Exception handling
@Service
public class ExceptionHandlingService {
    // Bad: Catching and swallowing exceptions
    @Transactional
    public void badExceptionHandling() {
        try {
            // Database operations
        } catch (Exception e) {
            // Swallowing exception
        }
    }
    
    // Good: Proper exception handling
    @Transactional(rollbackFor = Exception.class)
    public void goodExceptionHandling() {
        // Database operations
        // Let exceptions propagate
    }
}

// 3. Read-only transactions
@Service
public class ReadOnlyService {
    // Bad: Not using read-only
    @Transactional
    public List getUsers() {
        return userRepository.findAll();
    }
    
    // Good: Using read-only
    @Transactional(readOnly = true)
    public List getUsersReadOnly() {
        return userRepository.findAll();
    }
}
                

Conclusion

Spring's transaction management provides powerful features for ensuring data consistency and integrity. Understanding transaction concepts and following best practices helps in building reliable and efficient applications.