@Transactional 加了却没回滚?这 6 种失效场景最常踩
Spring 的声明式事务好用,但它有个特点:不生效的时候往往静悄悄的,既不报错也不回滚,直到脏数据进了库才被发现的。整理 6 个我在项目和面试里见得最多的失效场景。
先记住一条底层原理
@Transactional 靠的是代理(AOP)。Spring 在运行时把你的类包一层代理对象,事务的开启、提交、回滚都发生在代理调用真实方法的前后。
几乎所有失效场景,本质上都是「这次调用没走代理」。理解了这一点,下面这些坑基本都能自己推出来。
场景 1:方法不是 public 的
@Transactional
private void save() { ... } // ❌ 注解失效,且不报错
Spring 默认只对 public 方法创建事务代理。写成 private/protected,代理拦不到,事务形同虚设。CGLIB 能代理 protected,但 Spring 的事务拦截器主动忽略了非 public 方法。
场景 2:同类内部方法自调用
最高频的一个:
@Service
public class OrderService {
public void create() {
this.save(); // ❌ 走的是 this,不是代理,事务失效
}
@Transactional
public void save() { ... }
}
create() 内部用 this.save() 调用,绕过了代理对象,@Transactional 白写。
解法任选其一:
- 把
save()拆到另一个 Bean 里注入进来; - 注入自己的代理:
@Lazy @Resource OrderService self;然后self.save(); - 从容器里拿当前代理:
AopContext.currentProxy()(需开启exposeProxy = true)。
场景 3:异常被自己 catch 吞掉了
@Transactional
public void pay() {
try {
deduct();
riskCheck(); // 这里抛异常
} catch (Exception e) {
log.error("支付失败", e); // ❌ 异常没抛出,代理以为一切正常,照提交
}
}
回滚的触发条件是异常抛到了代理层。你在方法里把它 catch 掉,代理根本不知道出过事,事务正常 commit。要么别 catch,要么 catch 之后手动 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。
场景 4:抛的是 CheckedException,默认不回滚
@Transactional
public void transfer() throws BizCheckedException {
...
throw new BizCheckedException("余额不足"); // ❌ 默认不回滚!
}
Spring 默认只对 RuntimeException 和 Error 回滚,受检异常(Checked Exception)被视为"可预期的业务分支",不触发回滚。想让它回滚要显式声明:
@Transactional(rollbackFor = Exception.class)
生产项目里建议全局都加上 rollbackFor = Exception.class,免得踩这个反直觉的默认值。
场景 5:数据库/连接池本身不支持事务
- MySQL 表引擎是 MyISAM(不支持事务),换成 InnoDB;
- 多数据源场景,
@Transactional只管到了默认数据源,跨库需要分布式事务方案(Seata 等),别指望一个注解搞定两个库。
场景 6:Bean 压根没被 Spring 管理
public class FooService { // ❌ 忘了 @Service
@Transactional
public void bar() { ... }
}
类上少了 @Service/@Component,或者你是用 new FooService() 手动创建的实例,Spring 无从为它生成代理,注解自然无效。
一张速查表
| 场景 | 根因 | 一句话解法 |
|---|---|---|
| 非 public 方法 | 代理不拦截 | 改成 public |
| 同类自调用 | 绕过了代理 | 拆 Bean / 注入自身代理 |
| catch 吞异常 | 异常没抛到代理 | 别 catch 或 setRollbackOnly |
| Checked 异常 | 默认不回滚 | rollbackFor = Exception.class |
| MyISAM / 多数据源 | 底层不支持 | 换 InnoDB / 上分布式事务 |
| 未托管 Bean | 没有代理 | 加 @Service,别 new |
排查时如果全都对着还是不回滚,把 logging.level.org.springframework.transaction=DEBUG 打开,看代理到底有没有介入,基本就能定位了。
- 点赞
- 收藏
- 关注作者
评论(0)