惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
博客园_首页
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
罗磊的独立博客
腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
B
Blog
D
Docker
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
A
About on SuperTechFans
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
WordPress大学
WordPress大学
MyScale Blog
MyScale Blog
G
Google Developers Blog
博客园 - 司徒正美
aimingoo的专栏
aimingoo的专栏
小众软件
小众软件
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
M
MIT News - Artificial intelligence
Recent Announcements
Recent Announcements

博客园 - wastonl

maven选择settings.xml策略 RocketMQ消费机制整理 RocketMQ重平衡机制 @JacksonXmlElementWrapper使用限制 JSR303和SpringBoot参数校验机制 同一Java进程生产者和消费者与MqClientInstance对应关系 RocketMQ自动创建主题机制 SpringMVC异步请求 tomcat异步请求机制 Spring异步任务和定时任务 Spring ResolvableType说明 Jvm内存以及垃圾回收相关知识 RocketMQ如何保证消息可靠性 RocketMQ整体架构 mybatis-plus易忘点笔记 SpringMVC使用Resource实现二进制传输(下载) Spring事件异步执行设计与实现 Spring Bean销毁机制 Spring Lifecycle组件 Zipkin Brave使用 Spring Boot日志系统简要介绍 spring cloud sleuth基本使用 Spring懒加载与@Lazy注解 tomcat自动刷新响应输出流缓冲区 https碎碎念 ES脚本使用 SpringMVC静态资源处理 Maven插件运行方式 如何使用Maven将项目中的依赖打进jar包 时区以及时区对于Java时间类格式化的影响
Spring事务抛出UnexpectedRollbackException异常细节
wastonl · 2026-09-19 · via 博客园 - wastonl

基本概念

内层事务发生异常时, 外部事务即便try catch了内层事务,也会使得外层事务回滚,同时会抛出UnexpectedRollbackException

因为在外层事务角度看,它本来是要正常提交事务的,但是由于参与的内层事务出现异常,而它们又同属于一个真实的物理事务,Spring默认采取将事务回滚,同时抛出一个不期望的回滚异常。

这个行为通过globalRollbackOnParticipationFailure参数控制,默认为true。即参于事务失败时,则全局回滚。

执行流程

代码如下

@RequiredArgsConstructor
public class TxCase2 {

    private final TxCase3 txCase3 ;

    @Transactional
    public void execute() {
        try {
            txCase3.execute();
        } catch (RuntimeException e) {
            log.error(e.getMessage(), e);
        }
        log.info("====txCase2 execute====");
    }
}


public class TxCase3 {

    @Transactional
    public void execute() {
        log.info("====txCase3 execute====");
        throw new RuntimeException("模拟异常");
    }
}

执行结果

====txCase3 execute====	
java.lang.RuntimeException: 模拟异常
	at com.wangtao.springboottest.TransactionTest$TxCase3.execute(TransactionTest.java:119)
====txCase2 execute====
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

执行流程

txCase2 begin
txCase3 begin(只是参与到txCase2的事务)
txCase3 执行异常
txCase3 rollback(发现不是一个新开的事务, 根据globalRollbackOnParticipationFailure来决定是否标记全局回滚)
txCase2 执行结束(因为try catch了,整个方法并没有发生异常)
txCase2 commit(发现标记了全局回滚, 走回滚动作)
txCase2 rollback(unexpected=true) (此时unexpected为true)
如果unexpected=true,抛出UnexpectedRollbackException

failEarlyOnGlobalRollbackOnly作用

此参数的含义是,如果是全局回滚导致进入的rollback(unexpected参数=true),要不要将upexpected参数重置为false,不重置则会提前抛出UnexpectedRollbackException,只有在3层事务时才有效果,默认为false,当此参数为false时,会将upexpected参数重置为false,就不会在中间的参与事务中抛出UnexpectedRollbackException了。

比如事务3参与事务2,事务2参与事务1,事务2中的方法捕获了事务3方法中的异常。

执行流程如下

事务3方法发生异常 -> 事务3正常发生回滚(unexpected=false) -> 发现自己不是新事务 -> 标记全局回滚 -> unexpected=false不会抛出UnexpectedRollbackException

事务2由于try catch了 -> 事务2方法正常执行完毕 -> 事务2commit -> 发现全局回滚标记 -> 转而触发rollback(unexpected=true) -> 发现自己不是新事务 -> 重新标记全局回滚 -> 根据failEarlyOnGlobalRollbackOnly是否将unexpected重置为false -> 若不重置,则抛出UnexpectedRollbackException -> 若重置则rollback正常结束

若事务2抛出UnexpectedRollbackException -> 则事务1方法执行时遭遇异常结束 -> 发生回滚(unexpected=false) -> 发现自己是新事务 -> 回滚整个事务

若事务2不抛出UnexpectedRollbackException -> 则事务1方法正常执行结束 -> 事务1commit -> 发现全局回滚标记 -> 转而触发rollback(unexpected=true) -> 发现自己是新事务 -> 回滚整个事务 -> unexpected=true(抛出UnexpectedRollbackException)

@Slf4j
@SpringBootTest
public class TransactionTest {

    @Autowired
    private TxCase1 txCase1;

    @Test
    public void testGlobalRollback() {
        txCase1.execute();
    }

    @TestConfiguration
    public static class TransactionConfig {

        @Bean
        public MyPlatformTransactionManagerCustomizer myPlatformTransactionManagerCustomizer() {
            return new MyPlatformTransactionManagerCustomizer();
        }

        @Bean
        public TxCase3 txCase3() {
            return new TxCase3();
        }

        @Bean
        public TxCase2 txCase2(TxCase3 txCase3) {
            return new TxCase2(txCase3);
        }

        @Bean
        public TxCase1 txCase1(TxCase2 txCase2) {
            return new TxCase1(txCase2);
        }
    }

    /**
     * 配置事务管理器属性
     */
    public static class MyPlatformTransactionManagerCustomizer implements PlatformTransactionManagerCustomizer<AbstractPlatformTransactionManager> {

        @Override
        public void customize(AbstractPlatformTransactionManager transactionManager) {
            transactionManager.setFailEarlyOnGlobalRollbackOnly(true);
        }
    }

    @RequiredArgsConstructor
    public static class TxCase1 {

        private final TxCase2 txCase2;

        @Transactional
        public void execute() {
            txCase2.execute();
            log.info("====txCase1 execute====");
        }
    }

    @RequiredArgsConstructor
    public static class TxCase2 {

        private final TxCase3 txCase3 ;

        @Transactional
        public void execute() {
            try {
                txCase3.execute();
            } catch (RuntimeException e) {
                log.error(e.getMessage(), e);
            }
            log.info("====txCase2 execute====");
        }
    }

    public static class TxCase3 {

        @Transactional
        public void execute() {
            log.info("====txCase3 execute====");
            throw new RuntimeException("模拟异常");
        }
    }
}

failEarlyOnGlobalRollbackOnly设置为false的执行结果(默认情况)

====txCase3 execute====
java.lang.RuntimeException: 模拟异常
====txCase2 execute====
====txCase1 execute====

failEarlyOnGlobalRollbackOnly设置为true的执行结果

 ====txCase3 execute====
 java.lang.RuntimeException: 模拟异常
 ====txCase2 execute====
 org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

可以看到由于UnexpectedRollbackException被提前抛出,TxCase1中的execute方法在调用txCase2.execute()时会碰到UnexpectedRollbackException异常,导致后续代码不会执行。

此时TxCase3会因为异常而直接进入rollback(不再是先进入commit,然后发现是全局回滚再触发rollback动作),此时的unexpected参数为false,不会再重复抛出UnexpectedRollbackException异常了。