Spring事务失效:@Transactional自调用不生效的原因与4大失效场景
2026/9/8 19:06:11 网站建设 项目流程

Spring 事务失效:@Transactional 自调用不生效的原因与 4 大失效场景

本文通过一个「注册送积分」的真实案例,讲清楚 Spring 声明式事务的底层机制(AOP 动态代理),并系统梳理@Transactional失效的 4 大场景与对应解决方案。读完你会明白:为什么注解明明加了,事务却不生效。

一、问题案例

用户注册接口需要做两件事:写入 user 表、写入 point 表(送 500 积分)。期望两者在同一事务中,任何一步失败都整体回滚。

@ServicepublicclassUserService{@AutowiredprivateUserMapperuserMapper;@AutowiredprivatePointMapperpointMapper;@Transactionalpublicvoidregister(Useruser){userMapper.insert(user);// 写 user 表this.addPoints(user.getId());// 送积分}@TransactionalpublicvoidaddPoints(LonguserId){pointMapper.insert(userId,500);}}

线上某天 point 表插入失败,预期 user 表也回滚。但结果是:user 表数据留下了,积分却没到账。问题就出在this.addPoints()这一行。

二、原理详解:事务靠代理生效

Spring 的声明式事务不是靠方法本身起作用的,而是靠AOP 动态代理

  • 你在别处通过@Autowired注入的UserService,其实是一个代理对象(JDK 动态代理或 CGLIB 代理)。
  • 调用register时,代理对象先开启事务,再执行你的业务逻辑,最后提交或回滚。
  • this.addPoints()里的this指向的是原始对象(目标对象),不是代理对象。直接this调用跳过了代理,addPoints上的@Transactional等于没写,它既开启不了自己的事务,也加入不了register的事务。

一句话总结:同类内部自调用,事务注解全部失效。

三、实战代码:三种解决方案

方案一:拆到独立 Bean(最推荐)

把事务方法拆到另一个 Service,通过注入走代理,职责也更清晰:

@ServicepublicclassPointService{@AutowiredprivatePointMapperpointMapper;@TransactionalpublicvoidaddPoints(LonguserId){pointMapper.insert(userId,500);}}@ServicepublicclassUserService{@AutowiredprivatePointServicepointService;@Transactionalpublicvoidregister(Useruser){userMapper.insert(user);pointService.addPoints(user.getId());// 走代理,事务生效}}

方案二:注入自己(self-injection)

@ServicepublicclassUserService{@AutowiredprivateUserServiceself;// 注入自己的代理对象@Transactionalpublicvoidregister(Useruser){userMapper.insert(user);self.addPoints(user.getId());// 通过 self 走代理}}

能用,但自我注入比较绕,容易让后来维护的人看懵。

方案三:AopContext.currentProxy()

((UserService)AopContext.currentProxy()).addPoints(user.getId());

需要先开启@EnableAspectJAutoProxy(exposeProxy = true),侵入性强,不推荐。

四、@Transactional 失效的 4 大场景

场景原因解决
同类自调用this调用绕过代理拆 Bean 或注入自己
方法非 public代理只能拦截 public 方法改为 public
异常被 catch 吞掉异常没抛出,代理感知不到不要吞异常,或手动回滚
受检异常默认只对 RuntimeException 回滚配置 rollbackFor,或统一抛运行时异常

五、总结

@Transactional生效的本质是「通过代理对象调用」。一旦绕过代理——最典型的就是同类this自调用——事务就静默失效。排查事务失效时,先确认调用是否经过了代理,再看方法可见性、异常类型和传播行为。


我是无羡(小剑),全栈偏后端的独立开发者。

作品集:无羡 · 独立开发者作品集

如果对你有帮助,欢迎点赞、收藏、关注。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询