Spring Boot 事务管理

在 Spring Boot 框架里,@Transactional 注解是实现声明式事务管理的关键工具。下面从多个维度详细剖析这个注解,帮助你深入理解并熟练运用它。

1. 注解作用

借助 AOP(面向切面编程)技术,@Transactional 能够在方法执行前开启事务,方法成功执行后提交事务,若方法执行过程中出现异常则回滚事务。这样一来,业务代码就无需直接和事务 API 打交道,开发人员可以把更多精力放在业务逻辑上。

2. 核心属性

2.1 propagation(传播行为)

此属性用于确定事务方法和现有事务之间的关系,共有 7 种传播行为,常见的有以下几种:

  • REQUIRED(默认值):若当前不存在事务,就创建一个新事务;若存在事务,就加入该事务。
  • REQUIRES_NEW:不管当前是否存在事务,都会创建一个新事务,并且挂起当前事务。
  • SUPPORTS:如果当前存在事务,就加入该事务;如果不存在事务,就以非事务的方式执行。
  • NOT_SUPPORTED:以非事务的方式执行操作,若当前存在事务,就挂起该事务。
  • MANDATORY:必须在一个已存在的事务中执行,若当前不存在事务,就抛出异常。

2.2 isolation(隔离级别)

该属性用于控制事务之间的可见性,常见的隔离级别有:

  • DEFAULT(默认值):采用数据库的默认隔离级别(例如 MySQL 的 REPEATABLE_READ,Oracle 的 READ_COMMITTED)。
  • READ_UNCOMMITTED:允许读取尚未提交的数据变更,可能会导致脏读、不可重复读和幻读问题。
  • READ_COMMITTED:只允许读取已经提交的数据,可以避免脏读,但仍可能出现不可重复读和幻读。
  • REPEATABLE_READ:确保在同一个事务中多次读取同一数据的结果是一致的,可以避免脏读和不可重复读,但幻读仍有可能发生。
  • SERIALIZABLE:最高的隔离级别,通过强制事务串行执行来避免所有并发问题,但会降低数据库的性能。

2.3 rollbackFornoRollbackFor

  • rollbackFor:可以指定一个或多个异常类,当方法抛出这些异常时,事务会回滚。
  • noRollbackFor:可以指定一个或多个异常类,当方法抛出这些异常时,事务不会回滚。

2.4 readOnly

将事务标记为只读,有助于数据库优化器对查询进行优化,一般用于只读取数据的方法。

2.5 timeout

用于设置事务的超时时间(单位为秒),若事务执行时间超过该时间仍未完成,就会自动回滚。

3. 使用示例

3.1 基础用法

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // 默认传播行为是 REQUIRED,默认回滚规则是 RuntimeException
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        if (user.getAge() < 0) {
            throw new IllegalArgumentException("年龄不能为负数");
        }
    }
}

3.2 自定义传播行为和隔离级别

@Service
public class OrderService {

    @Autowired
    private ProductService productService;

    // 自定义传播行为和隔离级别
    @Transactional(
        propagation = Propagation.REQUIRES_NEW,
        isolation = Isolation.READ_COMMITTED,
        timeout = 30,
        rollbackFor = {SQLException.class, BusinessException.class}
    )
    public void processOrder(Order order) throws SQLException {
        productService.reduceStock(order.getProductId(), order.getQuantity());
        // 其他业务逻辑
    }
}

3.3 只读事务

@Service
public class UserQueryService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(readOnly = true)
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
}

4. 底层实现原理

  • AOP 代理:Spring 会为带有 @Transactional 注解的类创建代理对象。
  • TransactionInterceptor:这是一个拦截器,负责处理事务的开启、提交和回滚操作。
  • PlatformTransactionManager:这是事务管理器接口,不同的持久化框架需要使用不同的实现类(如 DataSourceTransactionManager 用于 JDBC,JpaTransactionManager 用于 JPA)。

5. 注意事项

5.1 自调用问题

在同一个类中,一个方法调用另一个带有 @Transactional 注解的方法,事务不会生效。这是因为 AOP 代理需要通过外部代理对象来调用才能生效。

@Service
public class SelfCallService {

    public void methodA() {
        // 自调用,事务不会生效
        methodB(); 
    }

    @Transactional
    public void methodB() {
        // 事务性操作
    }
}

解决办法

  • 注入自身代理对象
@Service
public class SelfCallService {

    @Autowired
    private SelfCallService self;

    public void methodA() {
        // 通过代理对象调用,事务生效
        self.methodB(); 
    }

    @Transactional
    public void methodB() {
        // 事务性操作
    }
}

5.2 异常处理

  • 未检查异常(RuntimeException 及其子类)和 Error 会触发默认的事务回滚。
  • 检查异常(如 IOException)不会触发默认的事务回滚,需要通过 rollbackFor 来指定。

5.3 事务边界

事务的开始和结束是由方法的调用和返回决定的,因此要确保事务方法的原子性,避免事务范围过大。

6. 常见错误场景

6.1 事务不生效

  • 方法不是 public 的。
  • 自调用问题。
  • 异常被捕获但没有重新抛出。

6.2 脏读/不可重复读/幻读

隔离级别设置不合理,例如在需要避免脏读的场景下使用了 READ_UNCOMMITTED

6.3 死锁

事务持有锁的时间过长,或者多个事务以不同的顺序获取锁,都可能导致死锁。

7. 最佳实践

  • 优先在服务层方法上使用 @Transactional 注解。
  • 保持事务方法的短小精悍,避免在事务中执行耗时操作(如网络调用)。
  • 合理设置隔离级别和传播行为。
  • 明确指定 rollbackFor 异常。
  • 对于只读操作,使用 readOnly = true 来提高性能。

通过深入理解 @Transactional 注解的各个属性和使用场景,你可以在 Spring Boot 应用中更高效地管理事务,确保数据的一致性和完整性。

posted @ 2025-05-30 09:59  杯酒-故人  阅读(141)  评论(0)    收藏  举报