生产中出现了 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 许可协议。转载请注明出处!