@Transactional 加了却没回滚?这 6 种失效场景最常踩

举报
yd_231768394 发表于 2026/10/22 21:24:44 2026/10/22
【摘要】 Spring 的声明式事务好用,但它有个特点:不生效的时候往往静悄悄的,既不报错也不回滚,直到脏数据进了库才被发现的。整理 6 个我在项目和面试里见得最多的失效场景。 先记住一条底层原理@Transactional 靠的是代理(AOP)。Spring 在运行时把你的类包一层代理对象,事务的开启、提交、回滚都发生在代理调用真实方法的前后。几乎所有失效场景,本质上都是「这次调用没走代理」。理解...

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 打开,看代理到底有没有介入,基本就能定位了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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