Spring 事务传播机制:别再只会 @Transactional 了

举报
Brian-Tony 发表于 2026/11/04 09:01:52 2026/11/04
【摘要】 Spring 事务传播机制:别再只会 @Transactional 了@Transactional 大概是 Spring 里用得最多的注解之一,但很多人只知道"加了它就有事务",一旦遇到嵌套调用、异常回滚、自调用失效等问题就抓瞎。今天来系统梳理一下 Spring 事务传播机制的核心知识点。 传播行为是什么?传播行为(Propagation)解决的是:当一个事务方法调用另一个事务方法时,事务...

Spring 事务传播机制:别再只会 @Transactional 了

@Transactional 大概是 Spring 里用得最多的注解之一,但很多人只知道"加了它就有事务",一旦遇到嵌套调用、异常回滚、自调用失效等问题就抓瞎。今天来系统梳理一下 Spring 事务传播机制的核心知识点。

传播行为是什么?

传播行为(Propagation)解决的是:当一个事务方法调用另一个事务方法时,事务应该如何传播。 Spring 定义了 7 种传播行为:

传播行为 行为描述
REQUIRED(默认) 有事务就加入,没有就新建
SUPPORTS 有事务就加入,没有就以非事务方式执行
MANDATORY 必须有事务,否则抛异常
REQUIRES_NEW 总是新建事务,挂起当前事务
NOT_SUPPORTED 以非事务方式执行,挂起当前事务
NEVER 以非事务方式执行,有事务则抛异常
NESTED 嵌套事务(基于 Savepoint),无事务时同 REQUIRED

最常用的两个:REQUIRED vs REQUIRES_NEW

这是面试高频题,也是实际开发中最容易踩坑的地方。

REQUIRED(默认):内外方法共享同一个物理事务,同生共死。内层抛异常会导致整个事务回滚。

REQUIRES_NEW:内层方法会挂起外层事务,开启一个全新的独立事务。内外互不影响——内层回滚不影响外层提交,外层回滚也不影响已提交的内层。

举个实际场景:

@Service
public class OrderService {
    @Autowired
    private LogService logService;

    @Transactional
    public void createOrder(Order order) {
        orderDao.insert(order);
        try {
            logService.saveLog("创建订单"); // 日志记录不应影响主流程
        } catch (Exception e) {
            log.error("日志记录失败", e);
        }
        // 即使日志失败,订单仍然提交
    }
}

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String msg) {
        logDao.insert(msg);
    }
}

这里日志记录用 REQUIRES_NEW,即使主订单事务回滚,日志仍然保留(方便排查问题);即使日志写入失败,也不影响订单提交。

容易踩的坑

1. 自调用失效

@Service
public class UserService {
    @Transactional
    public void outer() {
        this.inner(); // 直接调用,不走代理,事务不生效!
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void inner() {
        // ...
    }
}

Spring 事务基于 AOP 代理实现,this 调用绕过了代理,传播行为完全不生效。 解决办法:注入自身代理、用 AopContext.currentProxy(),或者拆到另一个 Bean 里。

2. 异常被 catch 吞掉

@Transactional
public void doSomething() {
    try {
        innerService.update(); // 内层抛异常
    } catch (Exception e) {
        // 吞掉了,事务不会回滚
    }
}

默认只有 RuntimeException 和 Error 才会触发回滚,受检异常需要显式指定 rollbackFor = Exception.class。

3. REQUIRES_NEW 的连接和锁风险

外层占一个连接,内层 REQUIRES_NEW 再占一个,高并发下连接池容易被打满。如果内外操作同一行数据,还可能引发死锁。

4. NESTED 不是 REQUIRES_NEW

NESTED 基于 JDBC Savepoint,是同一个事务内的"回滚点",不是独立事务。内层回滚只回到保存点,外层可以继续;但外层回滚会连带内层一起回滚。

传播行为 vs 隔离级别

这两个是正交的概念:

  • 传播行为:解决事务"边界"问题(有没有事务、是新事务还是加入现有事务)。
  • 隔离级别:解决并发事务之间的"可见性"问题(脏读、不可重复读、幻读)。

可以组合使用,但要注意:加入现有事务时,内层指定的隔离级别会被忽略,沿用外层事务的隔离级别。

一句话总结

  • 默认用 REQUIRED,需要独立事务边界时用 REQUIRES_NEW。
  • 自调用不走代理,传播行为会失效。
  • 默认只对 RuntimeException 回滚,受检异常要加 rollbackFor。
  • REQUIRES_NEW 会额外占用连接,高并发场景慎用。
  • NESTED 是保存点而非新事务,别和 REQUIRES_NEW 搞混。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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