在 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 只在遇到 RuntimeException 和 Error 时才会回滚。如果你捕获了异常但没有重新抛出,事务就不会回滚。
@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!
觉得有用就点个赞吧~