2024-03-03
源码分析
0

目录

Spring 事务源码分析
示例
问题原因
分析
总结

生产中出现了 org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 异常,结合源码分析下原因

Spring 事务源码分析

示例

代码之间的调用如下

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); }

接口调用后,出现以下异常

image.png

问题原因

在多个方法都有 @Transactional注解,A调用B,且把B的调用进行了try-catch

分析

从源码中开始分析,在 AbstractPlatformTransactionManager #processRollback 中,设置 unexpectedRollback 为 true 时,才会抛出此异常

image.png

继续查看源码的调用栈,找到其 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() 的校验上,我们继续分析此方法

image.png

通过断点,可以确认此处判断调用到 DataSourceTransactionManager #isRollbackOnly 方法,其核心的 rollbackOnly 参数为 true,所以上述的逻辑判断中才会执行进程回滚的动作

image.png

image.png

上述已然了解参数的定义,接下来查找下源码中如何设置成 true,从源码中发现如下代码设置为true

java
/** * Mark the resource transaction as rollback-only. */ public void setRollbackOnly() { this.rollbackOnly = true; }

查看代码的引用,发现一个熟悉的调用 DataSourceTransactionManager #setRollbackOnly

java
public void setRollbackOnly() { getConnectionHolder().setRollbackOnly(); }

image.png

从堆栈中查看调用信息,发现又回到了老朋友 #processRollback 方法上,

image.png

此处有个判断 status.hasTransaction() 结果为 true,表示存在事务,在这做个事务源码的延伸,这部分为多个事务之间嵌套的处理,源码处理的位置如下

image.png

继续查找堆栈信息,可以找到 completeTransactionAfterThrowing 方法

image.png

继续往上,就看到这是 try catch 部分,我们都知道事务底层实现就是aop,由于此问题出现的原因是嵌套事务,且嵌套中的事务抛出异常,当异常抛出后,就会被捕获,且将底层 rollbackOnly 设置为 true,此时接下来回归到到主线上调用 commitTransactionAfterReturning 方法进行提交

image.png

其实走到这就很明朗了,此处代码已经是文章开头追溯的那部分源码

image.png

总结

梳理下源码的流程,当嵌套内事务抛出异常时,会将全局仅回滚标记 rollbackOnly = true 设置为 true, 从源码的正常流程来说会把异常继续 throw,但是当我们外层事务 try catch 后,就导致异常无法抛出,最外层事务在执行后续 commit 时,会校验是否为全局回滚参数,抛出 throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");

image.png

本文作者:柳始恭

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!