生产中出现了 org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
异常,结合源码分析下原因
代码之间的调用如下
java @Transactional(rollbackFor = Exception.class)
public void test() {
// 测试出现 "Transaction rolled back because it has been marked as rollback-only"
try {
logService.testRockBack();
} catch (Exception e) {
System.out.println("try catch Exception : " + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testRockBack() {
int i = 0;
System.out.println(1/i);
}
接口调用后,出现以下异常
在多个方法都有 @Transactional
注解,A调用B,且把B的调用进行了try-catch
从源码中开始分析,在 AbstractPlatformTransactionManager #processRollback
中,设置 unexpectedRollback
为 true 时,才会抛出此异常
继续查看源码的调用栈,找到其 processRollback(defStatus, true)
方法处调用,入参为 true,那正常应该为不会调用到此方法,为什么此处会调用到呢?
我们接着分析下 !shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()
这个判断条件
java /**
* This implementation of commit handles participating in existing
* transactions and programmatic rollback requests.
* Delegates to {@code isRollbackOnly}, {@code doCommit}
* and {@code rollback}.
* @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
* @see #doCommit
* @see #rollback
*/
@Override
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
processCommit(defStatus);
}
查看方法源码 shouldCommitOnGlobalRollbackOnly()
的实现底层,发现默认返回 false,那么此判断条件中一定为 true, 不会影响判断结果,核心的问题还是出现在 defStatus.isGlobalRollbackOnly()
的校验上,我们继续分析此方法
通过断点,可以确认此处判断调用到 DataSourceTransactionManager #isRollbackOnly
方法,其核心的 rollbackOnly
参数为 true,所以上述的逻辑判断中才会执行进程回滚的动作
上述已然了解参数的定义,接下来查找下源码中如何设置成 true,从源码中发现如下代码设置为true
java /**
* Mark the resource transaction as rollback-only.
*/
public void setRollbackOnly() {
this.rollbackOnly = true;
}
查看代码的引用,发现一个熟悉的调用 DataSourceTransactionManager #setRollbackOnly
java public void setRollbackOnly() {
getConnectionHolder().setRollbackOnly();
}
从堆栈中查看调用信息,发现又回到了老朋友 #processRollback
方法上,
此处有个判断 status.hasTransaction()
结果为 true,表示存在事务,在这做个事务源码的延伸,这部分为多个事务之间嵌套的处理,源码处理的位置如下
继续查找堆栈信息,可以找到 completeTransactionAfterThrowing
方法
继续往上,就看到这是 try catch
部分,我们都知道事务底层实现就是aop,由于此问题出现的原因是嵌套事务,且嵌套中的事务抛出异常,当异常抛出后,就会被捕获,且将底层 rollbackOnly
设置为 true,此时接下来回归到到主线上调用 commitTransactionAfterReturning
方法进行提交
其实走到这就很明朗了,此处代码已经是文章开头追溯的那部分源码
梳理下源码的流程,当嵌套内事务抛出异常时,会将全局仅回滚标记 rollbackOnly = true
设置为 true, 从源码的正常流程来说会把异常继续 throw,但是当我们外层事务 try catch
后,就导致异常无法抛出,最外层事务在执行后续 commit
时,会校验是否为全局回滚参数,抛出 throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only")
;
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!