云计算百科
云计算领域专业知识百科平台

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

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

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

一、问题案例

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

@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private PointMapper pointMapper;

@Transactional
public void register(User user) {
userMapper.insert(user); // 写 user 表
this.addPoints(user.getId()); // 送积分
}

@Transactional
public void addPoints(Long userId) {
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,通过注入走代理,职责也更清晰:

@Service
public class PointService {
@Autowired
private PointMapper pointMapper;

@Transactional
public void addPoints(Long userId) {
pointMapper.insert(userId, 500);
}
}

@Service
public class UserService {
@Autowired
private PointService pointService;

@Transactional
public void register(User user) {
userMapper.insert(user);
pointService.addPoints(user.getId()); // 走代理,事务生效
}
}

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

@Service
public class UserService {
@Autowired
private UserService self; // 注入自己的代理对象

@Transactional
public void register(User user) {
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 自调用——事务就静默失效。排查事务失效时,先确认调用是否经过了代理,再看方法可见性、异常类型和传播行为。


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

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

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

赞(0)
未经允许不得转载:网硕互联帮助中心 » Spring事务失效:@Transactional自调用不生效的原因与4大失效场景
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!