Spring嵌套事务异常Transaction rolled back because it has been marked as rollback-only

项目场景:

在循环里面使用try-catch去捕获异常的时候,并且try里面调的方法它也使用了事务注解 @transactional或者用了事务切面AOP去实现方法事务注入


问题描述

我这里的业务需求是A方法里面调了B方法,并且A和B都有事务,当B出现异常的时候我想获取B的具体报错信息,但是catch这个B方法的时候发生了嵌套事务异常,A方法继续往上抛出导致Transaction rolled back because it has been marked as rollback-only(事务回滚,因为它已被标记为仅回滚)


示例代码如下:

    @transactional
    public void A(){
        try{
            B();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    @transactional
    public static void B(){
        System.out.println("测试");
    }

原因分析:

@Transactional的默认事务传播机制是REQUIRED ,并且B方法抛出异常的时候会标记rollback-only为true,然后A准备提交事务的时候发现rollback-only为true也会进行回滚并且把B抛出的异常信息给吞了。也就是我想要B的异常信息没给,只给了一个rollback-only信息给前端。


事务传播级别说明
PROPAGATION_REQUIRED如果当前没有事务,就创建一个事务,如果已经存在事务,就加入到这个事务。当前传播机制也是spring默认传播机制
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,就抛出异常。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套的事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作

解决方案:

1、删除try-catch(不推荐)

这个没什么好说的,就是不进行try-catch,但是很多业务就是需要自己捕获异常,不让用户看到这个异常信息,继续维持服务器的运行。

2、修改事务传播级别

网上最为流传的解决方法就是根据业务设置不同的事务传播级别,将B方法的注解改为REQUIRES_NEW(A和B不需要同一个事务,把事务分开)或者NESTED(A和B同一个事务,但是B回滚的时候A不会回滚),即:

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void B(){
        System.out.println("测试");
    }

REQUIRED

  • 假设在A⽅法存在⼀个当前事务,B⽅法的事务传播机制为REQUIRED,则B⽅法会合并到A⽅法的事务⾥执⾏。
  • A、B任意⼀个⽅法异常(默认是RuntimeException和Error)都会导致A、B的操作被回滚。
  • Spring事务管理器不会吞异常。
    B异常后会抛给A,A如果没有catch这个异常,会继续向上抛。如果A catch住了,Spring事务管理器会替A向上抛⼀个
    UnexpectedRollbackException。总之,⼀旦A、B回滚,A的调⽤⽅⼀定能收到⼀个异常感知到回滚。
    (问题所在)

REQUIRES_NEW

  • 如果B发⽣异常,B事务⼀定回滚,B的异常随后会抛给A,如果A catch住了这个异常,A不会回滚,否则A也会回滚。
  • 如果A发⽣异常,则只会回滚A,不会回滚B。

NESTED

  • 如果B异常,B⼀定回滚,B的异常随后会抛给A,如果A catch住了这个异常,A不会回滚,否则A也会回滚。这种情况和REQUIRES_NEW⼀样。
  • 如果A发⽣异常,则A、B都会回滚。

根据业务需求,后面两个传播级别是不符合我的需求的,因为会导致A或B的事务不回滚,第一个传播级别REQUIRED才符合但有问题,即第一个方法对我无效,大家可以先试试。

3、手动回滚(推荐)

这个是我在逛了无数的论坛和提问才发现的解决方案,在catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常
示例代码如下:

    @Transactional
    public void A(){
        try{
            B();
        }catch(Exception e){
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            e.printStackTrace();
        }
    }
    @Transactional
    public void B(){
        System.out.println("测试");
    }

参考文章:
事务传播级别(7种)
Spring事务嵌套引发的血案—Transaction rolled back because it has been marked as rollback-only

  • 6
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当出现 "Transaction rolled back because it has been marked as rollback-only" 的异常时,通常是因为事务在某个方法中被标记为只能回滚,但后续的代码却试图提交该事务,从而导致异常的抛出。 一个例子是在方法A中捕获了方法B抛出的异常,并继续执行后续的代码,最后正常提交事务。然而,由于AB使用同一个事务,在方法B执行时,该事务会被标记为rollback-only,然后方法A继续使用该事务并执行事务提交的操作,因此最终会抛出异常。 要解决这个问题,可以在方法A中将异常继续往上抛出,而不是在catch块中处理异常,这样事务就会正确地回滚。例如,在方法A的catch块中将异常重新抛出,或者将异常声明为方法A抛出的异常之一。 另外,还可以通过在方法B中使用@Transactional注解来确保方法B在出现异常事务正确回滚,以避免这个异常的出现。 总之,"Transaction rolled back because it has been marked as rollback-only" 异常通常是由于事务在被标记为rollback-only后继续提交引起的,可以通过将异常继续往上抛出或使用@Transactional注解来解决这个问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [COS——R.log](https://download.csdn.net/download/ktc7000/4424623)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Transaction rolled back because it has been marked as rollback-only](https://blog.csdn.net/hingli/article/details/118415444)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值