
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-injectionServicepublicclassUserService{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自调用——事务就静默失效。排查事务失效时先确认调用是否经过了代理再看方法可见性、异常类型和传播行为。我是无羡小剑全栈偏后端的独立开发者。作品集无羡 · 独立开发者作品集如果对你有帮助欢迎点赞、收藏、关注。