【小家java】Spring事务嵌套引发的血案—Transaction rolled back because it has been marked as rollback-only

【小家java】Spring事务嵌套引发的血案—Transaction rolled back because it has been marked as rollback-only1 概述相比大家一想到事务 就想到 ACID 或者也会想到 CAP 但笔者今天不讨论这个 哈哈 本文将从应用层面稍带一点源码 来解释一下我们平时使用事务事会遇到的问题 而本次我们讲的正式嵌套事务引发的问题 2 栗子我们如果使用了 spring 来管理我们的事务 将会使事务的管理变得异常的简单 比如如下方法就有事务 Transactiona Overridepubl

本文已被https://yourbatman.cn收录;程序员专用网盘https://wangpan.yourbatman.cn;公号后台回复“专栏列表”获取全部小而美的原创技术专栏

相关阅读

Netflix OSS套件一站式学习驿站(Eureka、Hystrix、Ribbon、Feign、Zuul…)

【小家Spring】源码分析Spring的事务拦截器:TransactionInterceptor和事务管理器:PlatformTransactionManager


每篇一句

技多不压身。知识只有质量,没有重量,压不死人的。

1、概述

想必大家一想到事务,就想到ACID,或者也会想到CAP。但笔者今天不讨论这个,哈哈~本文将从应用层面稍带一点源码,来解释一下我们平时使用事务遇到的一个问题但让很多人又很棘手的问题:Transaction rolled back because it has been marked as rollback-only,中文翻译为:事务已回滚,因为它被标记成了只回滚。囧,中文翻译出来反倒更不好理解了,本文就针对此种事务异常做一个具体分析:

看此篇博文之前,建议先阅读:【小家java】Spring事务不生效的原因大解读

2、栗子

我们如果使用了spring来管理我们的事务,将会使事务的管理变得异常的简单,比如如下方法就有事务:

@Transactional @Override public boolean create(User user) { 
     int i = userMapper.insert(user); System.out.println(1 / 0); //此处抛出异常,事务回滚,因此insert不会生效 return i == 1; } 

这应该是我们平时使用的一个缩影。但本文不对事务的基础使用做讨论,只讨论异常情况。但本文可以给读者导航到我的另外一篇博文,介绍了事务不生效的N种可能性:【小家java】spring事务不生效的原因大解读

看下面这个例子,将是我们今天讲述的主题:

@Transactional @Override public boolean create(User user) { 
     int i = userMapper.insert(user); personService.addPerson(user); return i == 1; } //下面是personService的addPerson方法,也是有事务的 @Transactional @Override public boolean addPerson(User user) { 
     System.out.println(1 / 0); return false; } 

这种写法是我们最为普通的写法,显然是可以回滚的。但是如果上面这么写:

 @Transactional @Override public boolean create(User user) { 
     int i = userMapper.insert(user); try { 
     personService.addPerson(user); } catch (Exception e) { 
     System.out.println("不断程序,用来输出日志~"); } return i == 1; } 

这里需要注意:如果我是这么写:

 @Transactional @Override public boolean addPerson(User user) { 
     userMapper.updateByIdSelective(user); try { 
     editById(user); } catch (Exception e) { 
     e.printStackTrace(); } return false; } @Transactional @Override public boolean editById(User user) { 
     System.out.println(1 / 0); return false; } 

也是不会产生上面所述的那个rollback-only异常的:

 @Transactional @Override public boolean addPerson(User user) { 
     try { 
     editById(user); } catch (Exception e) { 
     e.printStackTrace(); } return false; } @Transactional @Override public boolean editById(User user) { 
     userMapper.updateByIdSelective(user); System.out.println(1 / 0); return false; } 

但是,我们的updateByIdSelective持久化是生效了的。分析如下:

  1. 为什么update持久化生效?
    因为addPerson有事务,所以editById理论上也有事务应该回滚才对,但是由于上层方法给catch住了,所以是没有回滚的,所以持久化生效。

  2. 为何没发生roolback-only的异常呢?
    原因是因为editById的事务是沿用的addPerson的事务。所以其实上仍然是只有一个事务的,所以catch住不允许回滚也是没有任何问题的,因为事务本身是属于addPerson的,而不属于editById。

但是我们这么来玩:

 @Transactional @Override public boolean addPerson(User user) { 
     try { 
     personService.editById(user); } catch (Exception e) { 
     e.printStackTrace(); } return false; } @Transactional @Override public boolean editById(User user) { 
     userMapper.updateByIdSelective(user); System.out.println(1 / 0); return false; } 

就毫无疑问会抛出如下异常:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 

但这么玩,去掉addPerson方法的事务,只保留editById的事务呢?

@Override public boolean addPerson(User user) { 
     try { 
     personService.editById(user); } catch (Exception e) { 
     e.printStackTrace(); } return false; } @Transactional @Override public boolean editById(User user) { 
     userMapper.updateByIdSelective(user); System.out.println(1 / 0); return false; } 

发现rollback-only异常是永远不会出来的。

因此我们可以得出结论,rollback-only异常,是发生在异常本身才有可能出现,发生在子方法内部是不会出现的。因此这种现象最多是发生在事务嵌套里。


备注一点:如果你catch住后继续向上throw,也是不会出现这种情况的。


引发了这个血案。这是上面意思呢?其实很好解释:在create准备return的时候,transaction已经被addPerson设置为rollback-only了,但是create方法给抓住消化了,没有继续向外抛出,所以create结束的时候,transaction会执commit操作,所以就报错了。看看处理回滚的源码:

private void processRollback(DefaultTransactionStatus status, boolean unexpected) { 
       try { 
       boolean unexpectedRollback = unexpected; try { 
       triggerBeforeCompletion(status); if (status.hasSavepoint()) { 
       if (status.isDebug()) { 
       logger.debug("Rolling back transaction to savepoint"); } status.rollbackToHeldSavepoint(); } else if (status.isNewTransaction()) { 
       if (status.isDebug()) { 
       logger.debug("Initiating transaction rollback"); } doRollback(status); } else { 
       // Participating in larger transaction if (status.hasTransaction()) { 
       if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { 
       if (status.isDebug()) { 
       logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); } doSetRollbackOnly(status); } else { 
       if (status.isDebug()) { 
       logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); } } } else { 
       logger.debug("Should roll back transaction but cannot - no transaction available"); } // Unexpected rollback only matters here if we're asked to fail early if (!isFailEarlyOnGlobalRollbackOnly()) { 
       unexpectedRollback = false; } } } catch (RuntimeException | Error ex) { 
       triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); throw ex; } triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); // Raise UnexpectedRollbackException if we had a global rollback-only marker if (unexpectedRollback) { 
       throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only"); } } finally { 
       cleanupAfterCompletion(status); } } 
@Override public boolean addPerson(User user) { 
       System.out.println(1 / 0); return false; } 

因为有源码里这么一句话:status.isNewTransaction() 所以我尝试用一个新事务也是能解决这个问题的

@Transactional(propagation = Propagation.REQUIRES_NEW) @Override public boolean addPerson(User user) { 
       System.out.println(1 / 0); return false; } 

但有时候我们并不希望是这样子,怎么办呢?

这个时候其实可以不通过异常来处理,或者通过自定义异常的方式来处理。

如果某个子方法有异常,spring将该事务标志为rollback only。如果这个子方法没有将异常往上整个方法抛出或整个方法未往上抛出,那么改异常就不会触发事务进行回滚,事务就会在整个方法执行完后就会提交,这时就会造成Transaction rolled back because it has been marked as rollback-only的异常。

另外一种并不推荐的解决办法如下:

<property name="globalRollbackOnParticipationFailure" value="false" /> 

这个方法也能解决,但显然影响到全局的事务属性,所以极力不推荐使用。

如果isGlobalRollbackOnParticipationFailure为false,则会让主事务决定回滚,如果当遇到exception加入事务失败时,调用者能继续在事务内决定是回滚还是继续。然而,要注意是那样做仅仅适用于在数据访问失败的情况下且只要所有操作事务能提交

Tips:

Spring aop 异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获runtimeException的异常

换句话说:service上的事务方法不要自己try catch(或者catch后throw new runtimeExcetpion()也成)这样程序异常时才能被aop捕获进而回滚。

3、使用场景

事务的场景无处不在。而这种场景一般发生在for循环里面处理一些事情,但又不想被阻断总流程,这个时候要catch的话请一定注意了

4、最后

事务被spring包装得已经隐藏了很多细节,方便了我们的同时,也屏蔽了很多底层实现。因此有时候我们对源码多一些了解,能让我们解决问题的时候更加的顺畅


关注A哥

Author A哥(YourBatman)
个人站点 www.yourbatman.cn
E-mail
微 信 fsx
活跃平台 【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only 【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only
公众号 BAT的乌托邦(ID:BAT-utopia)
知识星球 BAT的乌托邦
每日文章推荐 每日文章推荐

BAT的乌托邦

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/199004.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午2:03
下一篇 2026年3月26日 下午2:03


相关推荐

  • DeepSeek+豆包+Kimi降AI指令怎么写?从0到1完整教程

    DeepSeek+豆包+Kimi降AI指令怎么写?从0到1完整教程

    2026年3月13日
    2
  • pycharm默认主题_pycharm设置背景

    pycharm默认主题_pycharm设置背景一、Pycharm基本设置(小白篇)1、打开Pycharm设置,【File】-【Settings】2、设置解释器,【File】-【Settings】-【Project:项目名字】-【ProjectInterpreter】-【设置图标】-【Add】-【浏览到目标解释器】,选择相应解释器即可。3、设置pycharm主题,【File】-【Settings】-【Appearance&Behavior】-【Appearance】;Theme:修改主题、Usecustomf

    2022年8月27日
    4
  • 最新版 Cursor 无限续杯教程,它来了!via 掘金人工智能本月最热 (author: 极客密码)

    最新版 Cursor 无限续杯教程,它来了!via 掘金人工智能本月最热 (author: 极客密码)

    2026年3月16日
    3
  • Android游戏引擎汇总

    Android游戏引擎汇总随着 Android 系统的使用越来越广泛 了解一下 Android 平台下的游戏引擎就非常有必要 而同时因为基于 Intelx86 的移动设备越来越多 我也非常关注支持 x86 的移动游戏引擎 然而就目前为止游戏引擎的数量已经非常之多 每个引擎都有不同的特征 价格 成熟度等 通过一些调研之后 我发现有非常多的游戏引擎可用于开发运行在 android 移动设备端的游戏 其中有些还支持 x86 系统 另外还有些通过简单的修

    2026年3月19日
    1
  • 【软件工程】敏捷宣言

    【软件工程】敏捷宣言转载 敏捷宣言以及敏捷开发的特点 鸿鹄当高远 博客园敏捷宣言敏捷宣言 也叫做敏捷软件开发宣言 正式宣布了对四种核心价值和十二条原则 可以指导迭代的以人为中心的软件开发方法 nbsp 敏捷宣言强调的敏捷软件开发的四个核心价值是 个体和互动高于流程和工具工作的软件高于详尽的文档客户合作高于合同谈判响应变化高于遵循计划敏捷开发的第一条价值观就是 以人为本 强调 个体 人 及 个体

    2026年3月19日
    4
  • 【spring】AOP实践[通俗易懂]

    【spring】AOP实践[通俗易懂]【spring】AOP实践

    2022年4月25日
    48

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号