原文地址:https://blog.csdn.net/f641385712/article/details/80445912

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;
  }

这里我们把别的service方法try住,不希望它阻断我们的程序继续执行。表面上看合乎情理没毛病,but:
这里写图片描述

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

@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持久化是生效了的。分析如下:

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

为何没发生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

1

但这么玩,去掉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);
    }
}

简单分析:addPerson()有事务,然后处理的时候有这么一句:
这里写图片描述
这个时候把参数unexpectedRollback置为false了,所以当create事务需要回滚的时候,如下:
这里写图片描述
所以,就之前抛出异常了,这个解释很合理了吧。因为之前事务被设置过禁止回滚了。然后遇到了这个问题,我们有没有解决办法呢?其实最简单的决绝办法是:

@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" />

1

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

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

Tips:

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

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

另外一种方案:
在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(这也是比较推荐的做法)
3、使用场景

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

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

[转]Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only的更多相关文章

  1. Spring事务嵌套引发的问题--Transaction rolled back because it has been marked as rollback-only

    转载https://blog.csdn.net/f641385712/article/details/80445912 读了两边才找到问题

  2. 【Spring】21、用spring目标对象处理Transaction rolled back because it has been marked as rollback-only

    在使用spring做事务管理时,很多人都会遇到这样一段异常: org.springframework.transaction.UnexpectedRollbackException: Transact ...

  3. “Transaction rolled back because it has been marked as rollback-only”

    spring的声明事务提供了强大功能,让我们把业务关注和非业务关注的东西又分离开了.好东西的使用,总是需要有代价的.使用声明事务的时候,一 个不小心经常会碰到“Transaction rolled b ...

  4. Transaction rolled back because it has been marked as rollback-only分析解决方法

    1. Transaction rolled back because it has been marked as rollback-only事务已回滚,因为它被标记成了只回滚<prop key= ...

  5. Transaction rolled back because it has been marked as rollback-only

    出现这种错误的原因 1.接口A 调用了接口B 2.接口B报异常了,没有在B里面进行try catch捕获 3.接口A对 接口B进行了try catch捕获 因为接口B报异常 会把当前事物A接口的事物( ...

  6. 【springcloud】Transaction rolled back because it has been marked as rollback-only

    问题: 一个ajax请求,发生系统错误,错误内容:Transaction rolled back because it has been marked as rollback-only 原因是调用的s ...

  7. Spring事务嵌套

    学习一下Spring的事务嵌套:https://blog.csdn.net/zmx729618/article/details/77976793 重点句子: Juergen Hoeller 的话:   ...

  8. 记一次org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only异常

    @Transactional(rollbackFor = Exception.class) @Overridepublic DubboResult<Boolean> productAddO ...

  9. Transaction rolled back because it has been marked as rollback-only 原因 和解决方案

    产生原因  , 1 serviceA 调用 serviceB 然后 B  抛出异常 ,B 所在的 事物 回滚,B 把当前可写 事物标记成 只读事物 , 2 如果 A 和B 是在 同一个事物环境,并且 ...

随机推荐

  1. Spring IOC 总结

    IOC 简介 IOC是(Inversion of Control,控制反转)的简写.Spring提供IOC容器,将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的的过度程序耦合.它由DI( ...

  2. ffmpeg音频视频转格式工具使用

    ffmpeg是音频视频编解码工具,是一个开源项目,可以改变视频格式,比如mp4格式转ogg格式(有格式工厂,多这个东西纯属自己娱乐一下) 官方网址:www.ffmpeg.org 下载后找到ffmpeg ...

  3. APT 信息收集——shodan.io ,fofa.so、 MX 及 邮件。mx记录查询。censys.io查询子域名。

    信息收集 目标是某特殊机构,外网结构简单,防护严密.经探测发现其多个子机构由一家网站建设公司建设. 对子域名进行挖掘,确定目标ip分布范围及主要出口ip. 很多网站主站的访问量会比较大.往往主站都是挂 ...

  4. moviepy改进的想码

    这个要比前一个厚实点. 更改视频亮度,增加字幕,去除音轨,淡入特效,转换,截取时间,控制位置,组合图框,合成多段, 嗯,很多都有了. from django.test import TestCase ...

  5. 微信小程序~扫码

    为了让用户减少输入,我们可以把复杂的信息编码成一个二维码,利用宿主环境wx.scanCode这个API调起微信扫一扫,用户扫码之后,wx.scanCode的success回调会收到这个二维码所对应的字 ...

  6. LeetCode 842. Split Array into Fibonacci Sequence

    原题链接在这里:https://leetcode.com/problems/split-array-into-fibonacci-sequence/ 题目: Given a string S of d ...

  7. am335x system upgrade kernel ethernet(四)

    1      Scope of Document This document describes ethernet hardware design and porting KZS8081 to ubo ...

  8. CLR 调试体系结构

    公共语言运行时 (CLR) 调试 API 专门用作操作系统内核的一部分. 在非托管代码中,当程序生成异常时,内核将暂停执行进程,并使用 Win32 调试 API 将异常信息传递给调试器. CLR 调试 ...

  9. Codevs 2800 送外卖(状压DP)

    2800 送外卖 时间限制: 2 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题目描述 Description 有一个送外卖的,他手上有n份订单,他要把n份东西,分别送达n ...

  10. 2019qbxt游记

    Day 1 2019.8.6 来到qbxt的第一天,虽然早就对宾馆的等级做好了准备,但是还是十分的失望,外观是真的很简陋,不过里面还好的,,可以凑合. 我竟然和lbh一个宿舍!!!这次外出学习必将不安 ...