加了 @Transactional,怎么事务还是没生效?

举报
Brian-Tony 发表于 2026/11/05 00:31:03 2026/11/05
【摘要】 下单接口明明加了 @Transactional,订单写进去了,扣库存却失败了,结果订单还在、库存没扣,数据就这么“半吊子”地留在了库里。第一次遇到这种问题时,我的第一反应也是:注解是不是写错位置了?后来才发现,问题往往不在注解本身,而在事务到底有没有真正跑起来。 先把流程画清楚一次正常的声明式事务,大致是这样走的:业务方调用 -> Spring 代理拦截 -> 开启事务 -> 执行业务方法 ...

下单接口明明加了 @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 一切正常,自然不会回滚。
  • 排查时先确认调用是否经过代理,再确认异常有没有真的抛出来。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。