原文地址: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. tp5中在where中使用in

    $where = array(); $where['id'] = array('in', $uid_str); $res = $this->db2->name('user')->wh ...

  2. 图记 2016.1.7 获取本地图片、Bitmap转image

    这几天完成的内容有: 1.“添加图片”按钮 2.添加图片功能 遇到的问题: 我想要将添加图片按钮放在右下角,所以采用了相对布局,但是问题随之二来,因为将导航栏设置成了半透明,所以图片放到右下角之后,半 ...

  3. 第五次博客作业——Alpha2项目的测试

    格式描述: 这个作业属于哪个课程 <课程的链接> 这个作业要求在哪里 <作业要求的链接> 团队名称 你的代码我的发 这个作业的目标 选取非自己所在团队的3个项目进行测试,并写出 ...

  4. 《TensorFlow2深度学习》学习笔记(四)对笔记二中的模型增加正确率展示

    全部代码如下:(红色部分为与笔记二不同之处) #1.Import the neccessary libraries needed import numpy as np import tensorflo ...

  5. 《Java设计模式》之代理模式 -Java动态代理(InvocationHandler) -简单实现

    如题 代理模式是对象的结构模式.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 代理模式可细分为如下, 本文不做多余解释 远程代理 虚拟代理 缓冲代理 保护代理 借鉴文章 ht ...

  6. 深度学习Keras框架笔记之激活函数详解

    激活函数也是神经网络中一个很重的部分.每一层的网络输出都要经过激活函数.比较常用的有linear,sigmoid,tanh,softmax等.Keras内置提供了很全的激活函数,包括像LeakyReL ...

  7. Maven模块化搭建总结

    1.Maven插件在eclipse的安装 windows——>preferences——>Maven——>installations——>add——>installati ...

  8. myeclipse常用快捷(持续更新)

    最近开始转用myeclipse,总结一下快捷方式:(我喜欢用的) [Ctrl+O]    显示类中方法和属性的大纲,能快速定位类的方法和属性,在查找Bug时非常有用. [Ctrl+M]    窗口最大 ...

  9. Permission denied (publickey,gssapi-keyex,gssapi-with-mic).错误的解决

    SSH登录提示 Permission denied (publickey,gssapi-keyex,gssapi-with-mic). 修改被登录的SSH服务器ssh配置,/etc/ssh/sshd_ ...

  10. SpringSecurity 整合 JWT

    项目集成Spring Security(一) 在上一篇基础上继续集成 JWT ,实现用户身份验证. 前言 前后端分离项目中,如果直接把 API 接口对外开放,我们知道这样风险是很大的,所以在上一篇中我 ...