UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

转载自:https://dongguabai.blog.csdn.net/article/details/114686998

PROPAGATION_REQUIRES_NEW:原有事务A新起事务B,事务B中的commit和rollback不会影响外部事务A的commit和rollback,相互独立,如果事务B抛出异常,肯定会影响外事务A的。

PROPAGATION_NESTED:表示嵌套事务,看如下示例:

ServiceA {  
    /** 
     * 事务属性配置为 PROPAGATION_REQUIRED 
     */  
    @Transactional(propagation=Propagation.REQUIRED) // 1
    void methodA() {
        insertData(); //2
        try {
            ServiceB.methodB();   //3
        } catch (SomeException) {  
            // 执行其他业务, 如 ServiceC.methodC();   //5
        }
        updateData(); //6
    }  
}

ServiceB {

    @Transactional(propagation=Propagation.NESTED)
    void methodB(){
        //
        updateData(); //4
    }
}

在上面的1,将开起新事务A,2的时候会插入数据,此时事务A挂起,没有commit,3的时候,使用PROPAGATION_NESTED传播,将在3点的时候新建一个savepoint保存2插入的数据,不提交。
如果methodB出现异常,将回滚4的操作,不影响2的操作,同时可以处理后面的5,6逻辑,最后一起commit: 2,5,6;
如果methodB没有出现异常,那么将一起commit: 2,4,6。

假如methodB使用的PROPAGATION_REQUIRES_NEW
那么B异常,会commit: 2,5,6,和NESTED一致,如果methodB没有出现异常,那么会先commit4,再commit:6,那么事务将分离开,不能保持一致,假如执行6报错,2和6将回滚,而4却没有被回滚,不能达到预期效果。



最近在项目中发现了一则报错:“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”。根据报错信息来看是spring框架中的事务管理报错:事务回滚了,因为它被标记为回滚状态。

报错原因:
多层嵌套事务中,如果使用了默认的事务传播方式,当内层事务抛出异常,外层事务捕捉并正常执行完毕时,就会报出rollback-only异常。

spring框架是使用AOP的方式来管理事务,如果一个被事务管理的方法正常执行完毕,方法结束时spring会将方法中的sql进行提交。如果方法执行过程中出现异常,则回滚。
spring框架的默认事务传播方式是 PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。

在项目中,一般我们都会使用默认的传播方式,这样无论外层事务和内层事务任何一个出现异常,那么所有的sql都不会执行。在嵌套事务场景中,内层事务的sql和外层事务的sql会在外层事务结束时进行提交或回滚。如果内层事务抛出异常e,在内层事务结束时,spring会把事务标记为“rollback-only”。这时如果外层事务捕捉了异常e,那么外层事务方法还会继续执行代码,直到外层事务也结束时,spring发现事务已经被标记为“rollback-only”,但方法却正常执行完毕了,这时spring就会抛出“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”。

代码示例如下:

Class ServiceA {

    @Resource(name = "serviceB")
    private ServiceB b;

    @Transactional
    public void a() {
        try {
            b.b()
        } catch (Exception ignore) {
        }
    }
}


Class ServiceB {

    @Transactional
    public void b() {
        throw new RuntimeException();
    }
}

当调用a()时,就会报出“rollback-only”异常。

解决方案:

  • 如果希望内层事务抛出异常时中断程序执行,直接在外层事务的catch代码块中抛出e;
  • 如果希望程序正常执行完毕,并且希望外层事务结束时全部提交,需要在内层事务中做异常捕获处理:
    @Transactional
    public void b() {
        try {
            throw new RuntimeException();
        }catch (Exception e) {  
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
  • 如果希望内层事务回滚,但不影响外层事务提交,需要将内层事务的传播方式指定为Propagation.REQUIRES_NEW(A和B不需要同一个事务,把事务分开)或者NESTED(A和B同一个事务,但是B回滚的时候A不会回滚)。注:PROPAGATION_NESTED基于数据库savepoint实现的嵌套事务,外层事务的提交和回滚能够控制嵌内层事务,而内层事务报错时,可以返回原始savepoint,外层事务可以继续提交。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值