加了 @Transactional,怎么事务还是没生效?
下单接口明明加了 @Transactional,订单写进去了,扣库存却失败了,结果订单还在、库存没扣,数据就这么“半吊子”地留在了库里。第一次遇到这种问题时,我的第一反应也是:注解是不是写错位置了?后来才发现,问题往往不在注解本身,而在事务到底有没有真正跑起来。
先把流程画清楚
一次正常的声明式事务,大致是这样走的:
业务方调用 -> Spring 代理拦截 -> 开启事务 -> 执行业务方法 -> 正常提交 / 异常回滚
| 环节 | 正常情况 | 出问题时的表现 |
|---|---|---|
| 代理拦截 | 外部调用先经过代理 | 同类内自调用直接走了原方法,代理没机会拦截 |
| 开启事务 | 方法执行前绑定事务 | 方法不是 public,或对象根本不是 Spring Bean |
| 提交回滚 | 运行时异常触发回滚 | 异常被 catch 吃掉,或只抛了受检异常 |
只要其中一环断了,@Transactional 就只剩下一个“看起来很保险”的注解,数据库可不会替你兜底。
一个最小的例子
@Service
public class OrderService {
@Transactional
public void createOrder() {
orderRepository.save(new Order());
stockService.decrease(); // 这里抛运行时异常时,两步会一起回滚
}
}
这段代码成立的前提很关键:createOrder 必须被外部通过 Spring 代理调用。如果是同一个类里的另一个方法直接调用它,代理就被绕过去了,事务自然不会开启。把事务方法拆到另一个 Service 里、让调用走代理,是最省事的修复方式。
常见坑
第一个坑是自调用。A 方法调同一个类的 B 方法,即使 B 上加了 @Transactional,也不会生效,因为这次调用没有经过代理。第二个坑是异常被吃掉。业务代码里 catch 住异常只记日志、不再往外抛,Spring 就会认为方法正常结束了,于是正常提交,前面写进去的数据也就留下了。第三个坑是异常类型。默认情况下,Spring 只对运行时异常和 Error 回滚,如果业务抛的是受检异常,又没有声明 rollbackFor,事务同样不会回滚。第四个坑是方法或对象本身的问题:private 方法、final 方法、没有被 Spring 管理的对象,代理都无从下手。第五个坑是另起线程或异步执行,事务上下文不会自动跟过去,新线程里做的写操作和原事务不是一回事。
一句话总结
- @Transactional 生效的前提是走 Spring 代理,而不是方法本身带了这个注解。
- 同类自调用、private 方法、非 Spring Bean,都会让事务悄悄失效。
- 默认只对运行时异常回滚,受检异常需要显式声明 rollbackFor。
- 异常被吞掉,就等于告诉 Spring 一切正常,自然不会回滚。
- 排查时先确认调用是否经过代理,再确认异常有没有真的抛出来。
- 点赞
- 收藏
- 关注作者
评论(0)