问题现象
最近在开发电商订单模块时,遇到一个诡异现象:
@Transactional
public void createOrder(OrderDTO dto) {
// 1.保存订单主表
orderMapper.insert(dto);
// 2.扣减库存(抛出运行时异常)
inventoryService.deductStock(dto.getSkuId());
// 3.记录操作日志(即使事务回滚,日志依然入库)
logService.addLog("生成订单");
}
预期效果:当库存不足时,订单数据和日志记录应同时回滚
实际结果:订单数据回滚,但操作日志表却成功写入!
排查过程
1. 检查基础配置
✅ 确认启动类已添加@EnableTransactionManagement
✅ 检查数据库引擎为InnoDB(MyISAM不支持事务)
2. 代理机制验证
通过Debug查看Service实例类名:
System.out.println(this.getClass().getName());
// 输出结果:OrderServiceImpl$$EnhancerBySpringCGLIB$$...
结论:CGLIB代理生效(若为JDK动态代理需检查接口实现)
六大失效场景及修复方案
场景1:非public方法使用@Transactional
// ❌ 错误示例
@Transactional
private void saveOrder(Order order) {
//...
}
原理:Spring AOP无法代理私有方法
修复:改为public访问权限
场景2:异常类型不匹配
@Transactional
public void createOrder() throws Exception {
//...
throw new SQLException(); // 检查异常
}
原理:默认只回滚RuntimeException和Error
修复:
@Transactional(rollbackFor = Exception.class)
场景3:同一类内方法调用
public void processOrder() {
this.createOrder(); // 内部调用不走代理
}
@Transactional
public void createOrder() {...}
原理:自调用绕过AOP代理
修复方案:
- 注入自身代理对象
@Autowired
private OrderService selfProxy;
public void processOrder() {
selfProxy.createOrder();
}
- 使用AopContext(需开启exposeProxy)
((OrderService)AopContext.currentProxy()).createOrder();
场景4:多线程上下文丢失
@Transactional
public void asyncProcess() {
new Thread(() -> {
orderMapper.insert(...); // ❌ 新线程脱离事务控制
}).start();
}
修复:使用TransactionTemplate手动管理
@Autowired