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

先看问题

存在一个A方法(带有事物REQUIRED),B方法也存在事物REQUIRED,A方法调用B方法,担心B方法中会报错,就进行了try(){}处理,也没有往上层去抛,这个时候执行结果是什么

重现问题

走到事故现场,重现那段惨案。

A方法调用了B方法,A和B都存在事物,并且在A调用的时候把B进行了try(){}操作

B方法:

测试类登场:

01

运行结果

程序报错,数据也没有插入数据库。


按照我的想法addCustMegreWaitB()出现了错误,但是我们在addCustMegreWaitA()方法中进行了捕获也即是try()catch{},预想程序会成功执行,只不过addCustMegreWaitB()方法中的数据插入不到数据库,但是addCustMegreWaitA()是可以的。

还是我太天真与年轻,真相确不是这样。。。。残酷的现实狠狠的给我了一记耳光。


数据库:

查看日志文件:

Transaction rolled back because it has been marked as rollback-only,中文翻译为:事务已回滚,因为它被标记成了只回滚

原因分析

出现了问题,就要分析问题,然后解决问题。


在addCustMegreWaitA方法准备提交的时候,transaction已经被addCustMegreWaitB方法设置为rollback-only了,但是addCustMegreWaitA方法中将addCustMegreWaitB的报错将给抓住消化了,没有继续向外抛出,所以addCustMegreWaitA结束的时候,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);
    }
}

复制代码

 简单分析:addCustMegreWaitB()有事务,然后处理的时候有这么一句:



这个时候把参数unexpectedRollback置为false了,所以当addCustMegreWaitA()事务需要提交的时候


所以,就之前抛出异常了。


3分析总结


当A方法的事物(REQUIRED),B方法的事物(REQUIRED),A调用B方法,在spring中,spring将会把这些事务合二为一。当整个方法中每个子方法没报错时,整个方法执行完才提交事务,

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

如何解决这个问题

1.将这个addCustMegreWaitB()中的错误try(){}后继续往上层去抛,交给上层去处理掉,不能内部消化


2.将addCustMegreWaitB()中的事物改为(REQUIRES_NEW)

代码验证一下这种方式:



运行结果:


程序执行正常。数据库插入了一条数据,addCustMegreWaitB()方法中进行 了回滚。

加入51码农网。www.51manong.com 寻找一起进步的人,在编程的道路上从此不孤单。

关注公众号:51码农网

转载于:https://juejin.im/post/5c5249c56fb9a04a0e2d9cda

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值