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

问题产生背景

有用户反馈在系统中操作修改时有报错,查询日志发现报错信息如下。看起来就是事务回滚了,因为被标记为只能回滚。

报错信息

2024-11-13 10:45:59.743 ERROR  [io-8080-exec-40] .c.c.c.AbstractExceptionAdvice : Transaction rolled back because it has been marked as rollback-only
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:870) ~[spring-tx-5.3.9.jar!/:5.3.9]
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:707) ~[spring-tx-5.3.9.jar!/:5.3.9]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654) ~[spring-tx-5.3.9.jar!/:5.3.9]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407) ~[spring-tx-5.3.9.jar!/:5.3.9]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.9.jar!/:5.3.9]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar!/:5.3.9]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.9.jar!/:5.3.9]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.9.jar!/:5.3.9]
	at com.serviceWrapper$$EnhancerBySpringCGLIB$$43f15635.update(<generated>) ~[manage-2.1.3-RELEASE.jar!/:2.1.3-RELEASE]

2024-11-13 10:45:59.383 ERROR  [io-8080-exec-40] ConfigService : error for 
### Error flushing statements.  Cause: org.apache.ibatis.executor.BatchExecutorException: com.ConfigMapper.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: Duplicate entry 'xxxx' for key 'uk_code'
### Cause: org.apache.ibatis.executor.BatchExecutorException: com.ConfigMapper.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: Duplicate entry 'xxxx' for key 'uk_code'
org.apache.ibatis.exceptions.PersistenceException: 
### Error flushing statements.  Cause: org.apache.ibatis.executor.BatchExecutorException: com.ConfigMapper.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: Duplicate entry 'xxxx' for key 'uk_code'
### Cause: org.apache.ibatis.executor.BatchExecutorException: com.ConfigMapper.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: Duplicate entry 'xxxx' for key 'uk_code'
	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) ~[mybatis-3.5.1.jar!/:3.5.1]
	at org.apache.ibatis.session.defaults.DefaultSqlSession.flushStatements(DefaultSqlSession.java:254) ~[mybatis-3.5.1.jar!/:3.5.1]
	at com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.saveBatch(ServiceImpl.java:128) ~[mybatis-plus-extension-3.1.0.jar!/:3.1.0]
	at com.baomidou.mybatisplus.extension.service.IService.saveBatch(IService.java:58) ~[mybatis-plus-extension-3.1.0.jar!/:3.1.0]
	at com.baomidou.mybatisplus.extension.service.IService$$FastClassBySpringCGLIB$$f8525d18.invoke(<generated>) ~[mybatis-plus-extension-3.1.0.jar!/:3.1.0]

问题定位

搜了一下解释,是说事务在提交的时候报错了,因为被标记为只能回滚,无法提交。按照一般的情况,是有异常抛出,事务回滚。当前这个场景,是异常抛出来,被事务管理器标记为只能回滚,但是程序又正常执行了,最后事务提交的时候报错了。

由以上排查,开始梳理代码逻辑(如下伪代码)。有看到报错信息里面有另一行error日志是C.c1方法打印的,但是这个异常已经被catch吞掉了,不会抛出。因为在设计上这里是弱业务,不需要影响主逻辑执行。所以就很奇怪,明明没有异常抛出,事务却被标记为只回滚了。


    class A {
        public void a1() {
            // 业务处理
            B.b1();
            // 后置处理
        }
    }

    class B {
        @Transactional(rollbackFor = Exception.class)
        public void b1() {
            // 本地事务执行
            // 外部服务调用
            RPC.rpc();
            // 弱业务处理,有异常不回滚
            C.c1();
        }
    }

    class C {
        public void c1() {
            try {
                // mybstisPlus更新数据库
            } catch (Exception e) {
                // 吞掉异常
                log.error("更新数据库失败", e);
            }
        }
    }
    
    class RPC {
        public void rpc() {
            // 业务逻辑
        }
    }

我当时傻傻的一直在看其他方法,就是没想到去看C.c1里面的逻辑,因为是直接调mybatisPlus的,直到一位同事路过提醒了一句, 点进去看了一眼,破案。mybatisPlus有加事务注解,并且抛出了异常,所以外面C.c1方法catch异常的时候,事务管理器已经被标记为回滚了。

 解决方案

将C.c1方法设置为以非事务方式执行。

    class C {
        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        public void c1() {
            try {
                // mybstisPlus更新数据库
            } catch (Exception e) {
                // 吞掉异常
                log.error("更新数据库失败", e);
            }
        }
    }

思考 

排查问题的时候不要固定思维,想当然。之前自己看的时候就天然默认mybatisPlus公共的里面不会有啥东西,一直在问题方法外面找哪里抛异常了,其实这是不符合事实的,如果外面有抛异常事务就会被正常回滚,而不会程序全执行完了去尝试提交事务。反而是catch吞掉异常那里,是最有问题的。下次注意!(一定🐶

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值