恋上蓝花楹

Spring Boot @Transactional 注解的五个常见陷阱

在 Spring Boot 开发中,@Transactional 注解是我们最常用的注解之一。它让事务管理变得简单优雅,但如果不了解其底层机制,很容易掉进一些”坑”里。今天我们来聊聊这些常见陷阱以及如何避免。

陷阱一:同类方法调用导致事务失效

这是最经典的问题。假设你有一个 Service 类:

@Service
public class OrderService {
    
    public void placeOrder(Order order) {
        // 业务逻辑
        saveOrder(order);
        // 即使 saveOrder 有 @Transactional,这里也不会生效
    }
    
    @Transactional
    public void saveOrder(Order order) {
        orderRepository.save(order);
    }
}

问题在于:Spring 的事务是通过 AOP 代理实现的,只有通过代理调用方法,事务才会生效。同类内部调用绕过了代理,自然也就没有事务了。

解决方案:

  • 将需要事务的方法抽取到另一个 Service
  • 使用 AopContext.currentProxy() 获取代理对象
  • 自己注入自己:通过 @Autowired 注入当前类

陷阱二:异常被吞掉了

默认情况下,@Transactional 只在遇到 RuntimeExceptionError 时才会回滚。如果你捕获了异常但没有重新抛出,事务就不会回滚。

@Transactional
public void process() {
    try {
        // 数据库操作
        doSomething();
    } catch (Exception e) {
        log.error("处理失败", e);
        // 异常被吞掉了,事务不会回滚!
    }
}

解决方案:

  • 不要在事务方法内部吞掉异常
  • 如果必须捕获,手动调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
  • 使用 @Transactional(rollbackFor = Exception.class) 扩大回滚范围

陷阱三:事务传播行为理解错误

最常用的传播行为是 REQUIRED(默认):如果当前存在事务,就加入;否则新建一个。但在某些场景下,你可能需要其他行为:

  • REQUIRES_NEW:总是新建事务,挂起当前事务。常用于日志记录等独立操作
  • NESTED:嵌套事务,可以独立回滚但不独立提交
  • SUPPORTS:有事务就加入,没有就以非事务方式执行

陷阱四:只读事务误用

@Transactional(readOnly = true) 并不是说不让写操作,而是告诉数据库驱动和 Hibernate 这是一个只读操作,可以进行优化。在 MySQL 中,它会阻止脏读,提高查询效率。

但要注意:如果你在只读事务中执行了写操作,不同数据库的行为可能不一致,有些会报错,有些只是忽略。

陷阱五:事务范围过大

有些人喜欢在 Service 类上加 @Transactional,让所有方法都有事务。这会带来几个问题:

  • 不必要的数据库连接占用
  • 锁持有时间过长,影响并发
  • 事务日志膨胀,影响性能

最佳实践:只在真正需要事务的方法上加注解,保持事务范围最小化。

总结

@Transactional 用起来简单,但要真正用好需要理解其原理。记住这几点:

  • 同类调用绕过代理,事务失效
  • 异常被吞掉,事务不回滚
  • 理解传播行为,选对场景
  • 事务范围越小越好

掌握了这些,你的 Spring Boot 应用会更加健壮可靠。Happy Coding!

wulilele

我是一名热爱科技与AI的软件工程师。