摘要:注解@Transactional嵌套事务失效,抛出异常ransaction rolled back because it has been marked as rollback-only,解决办法内部事务开启新事务。

问题描述

  有段事务嵌套的代码,每次执行完都会报“Transaction rolled back because it has been marked as rollback-only”异常:


2022-04-02 07:01:47.810 [http-nio-7032-exec-1] INFO -c.c.i.impl.BarServiceImpl - 调用抛异常的方法
2022-04-02 07:01:47.819 [http-nio-7032-exec-1] INFO -c.c.i.impl.FarServiceImpl - 我抛出 runtime exception
2022-04-02 07:01:47.819 [http-nio-7032-exec-1] ERROR-c.c.i.impl.BarServiceImpl - 抛异常了,emo,
2022-04-02 07:01:47.819 [http-nio-7032-exec-1] INFO -c.c.i.impl.BarServiceImpl - 事务正常回滚?
2022-04-02 07:01:47.825 [http-nio-7032-exec-1] ERROR-c.c.i.m.a.w.ControllerExceptionAdvice - error
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)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:707)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

  更多事务失效的场景,请戳《注解@Transactional事务失效的常见场景》。

问题分析

  错误代码如下:

@Slf4j
@Service
public class BarServiceImpl implements BarService { @Autowired
private FarService farService; @Override
@Transactional
public void bar() {
log.info("调用抛异常的方法");
try {
farService.far();
} catch (Exception e) {
log.error("抛异常了,emo,", e.getMessage());
}
log.info("事务正常回滚?"); }
}

  FarService中far函数的实现如下:

@Slf4j
@Service
public class FarServiceImpl implements FarService {
@Override
@Transactional
public void far() {
log.info("我抛出 runtime exception");
throw new NullPointerException("空指针了");
}
}

  实现逻辑分析

1.两个添加 @Transactional的方法有调用关系,在接口BarService调用了接口FarService;

2.far()报异常了,但没有进行try catch捕获;

3.在bar() 中进行了try catch捕获。

  在这种情况下,外层事务(BarService)和内层事务(FarService)共用同一个事务,任何一个出现异常,都会在bar()执行完毕后回滚。因为far()报异常,会把当前事务标志成rollback-only,所以,bar()的事务也需要回滚。由于bar()方法捕捉异常后一直往下走,等方法结束commit的时候会报错,提示:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

解决办法

  小编Wiener在此提供三种解决策略:

1.被调用方法自己对异常做try catch处理。

2.修改事务传播行为。如果希望内层事务回滚,但不影响外层事务提交,需要将内层事务新开一个事务,例如@Transactional的事务传播行为修改为 @Transactional(propagation = Propagation.REQUIRES_NEW)

3.在外层事务不对内层事务的方法far()进行异常捕获,这样会自动抛出内层事务的错误,而不是报UnexpectedRollbackException。

源码分析

  如果内部事务状态是PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY,将会在外层事务中运行,回滚的时候,并不执行回滚,只是标记一下回滚状态,当外层事务提交的时候,会先判断ConnectionHolder中的回滚状态,如果已经标记为回滚,则不会提交,而是外层事务进行回滚

  查看异常信息,我们知道UnexpectedRollbackException是从类AbstractPlatformTransactionManager.java 的 line 870 抛出的,源码如下:

	/**
* Process an actual rollback.
* The completed flag has already been checked.
* @param status object representing the transaction
* @throws TransactionException in case of rollback failure
*/
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);
}
}

  由此可见,内部事务因为抛异常,已经把事务标记为rollback-only。而unexpectedRollback为true,则是由调用方传入的,查看调用方AbstractPlatformTransactionManager.commit代码可知,直接传入了 processRollback(defStatus, true)

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

结束语

  工作中处处都需要学习,有时候看似简单的一个异常问题排查,可以让你深入学习后收获各种知识。所以在学习中请不求甚解,用阅读源码的实际行动,打破坐吃山空的思维,不仅要了解这个知识点,也要熟悉为什么要这么做。如此以来,遇到异常问题的时候才可以避免像铁拳打到棉花,无所着力。

@Transactional嵌套事务失效异常Transaction rolled back because it has been marked as rollback-only的更多相关文章

  1. @Transactional事务回滚异常:Transaction rolled back because it has been marked as rollback-only

    问题描述 事务设置手动回滚:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 代码需要返回比较友好的提示,但t ...

  2. 【Spring】21、用spring目标对象处理Transaction rolled back because it has been marked as rollback-only

    在使用spring做事务管理时,很多人都会遇到这样一段异常: org.springframework.transaction.UnexpectedRollbackException: Transact ...

  3. [转]Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only

    原文地址:https://blog.csdn.net/f641385712/article/details/80445912 1.概述 想必大家一想到事务,就想到ACID,或者也会想到CAP.但笔者今 ...

  4. “Transaction rolled back because it has been marked as rollback-only”

    spring的声明事务提供了强大功能,让我们把业务关注和非业务关注的东西又分离开了.好东西的使用,总是需要有代价的.使用声明事务的时候,一 个不小心经常会碰到“Transaction rolled b ...

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

    出现这种错误的原因 1.接口A 调用了接口B 2.接口B报异常了,没有在B里面进行try catch捕获 3.接口A对 接口B进行了try catch捕获 因为接口B报异常 会把当前事物A接口的事物( ...

  6. Transaction rolled back because it has been marked as rollback-only分析解决方法

    1. Transaction rolled back because it has been marked as rollback-only事务已回滚,因为它被标记成了只回滚<prop key= ...

  7. 【springcloud】Transaction rolled back because it has been marked as rollback-only

    问题: 一个ajax请求,发生系统错误,错误内容:Transaction rolled back because it has been marked as rollback-only 原因是调用的s ...

  8. 记一次org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only异常

    @Transactional(rollbackFor = Exception.class) @Overridepublic DubboResult<Boolean> productAddO ...

  9. Transaction rolled back because it has been marked as rollback-only 原因 和解决方案

    产生原因  , 1 serviceA 调用 serviceB 然后 B  抛出异常 ,B 所在的 事物 回滚,B 把当前可写 事物标记成 只读事物 , 2 如果 A 和B 是在 同一个事物环境,并且 ...

  10. Spring事务嵌套引发的问题--Transaction rolled back because it has been marked as rollback-only

    转载https://blog.csdn.net/f641385712/article/details/80445912 读了两边才找到问题

随机推荐

  1. idle如何调试程序

    1.启动idle ctrl+n 快捷键 新建命令窗口 输入程序 4.F5 调试程序,结果看在启动界面查看

  2. mysql order by 中文排序

    前言 在 MySQL 中,我们经常会对一个字段进行排序查询,但进行中文排序和查找的时候,对汉字的排序和查找结果往往都是错误的. 这种情况在 MySQL 的很多版本中都存在. 如果这个问题不解决,那么 ...

  3. python正则表达式笔记2

    由 '\' 和一个字符组成的特殊序列在以下列出. 如果普通字符不是ASCII数位或者ASCII字母,那么正则样式将匹配第二个字符.比如,\$ 匹配字符 '$'. \number匹配数字代表的组合.每个 ...

  4. OpenGL与GLSL各版本对应说明

    OpenGL 4.6 (API Core Profile) (API Compatibility Profile) OpenGL Shading Language 4.60 Specification ...

  5. 接口中的成员特点、类和接口之间的各种关系--java进阶day02

    1.接口的成员特点 1.接口没有构造方法 接口没有构造方法,但是实现类中有构造方法,super()又该访问谁呢? 类实现接口只是认干爹,类本身还是会有亲爹Object,super()会访问Object ...

  6. final关键字、Object类--java进阶day01

    1.规则 被final修饰的变量,名称都要大写,多单词的名称则需_来分隔 1.修饰方法 method方法已经不能被重写了,因为修饰该方法的是final 2.修饰类 当一个类中所有的成员方法都不想被重写 ...

  7. 一些 NuGet 包

    Some RestSharp Simple REST and HTTP API Client Newtonsoft.Json Json.NET is a popular high-performanc ...

  8. Spring AI 增加混元 embedding 向量功能

    上次我们讨论了如何将自己的开源项目发布到 Maven 中央仓库,确保其能够方便地被其他开发者使用和集成.而我们的项目 spring-ai-hunyuan 已经具备了正常的聊天对话功能,包括文本聊天和图 ...

  9. 11. RabbitMQ 消息队列 Federation (Exchange 交换机和 Queue队列) + Shovel 同步的搭建配置

    11. RabbitMQ 消息队列 Federation (Exchange 交换机和 Queue队列) + Shovel 同步的搭建配置 @ 目录 11. RabbitMQ 消息队列 Federat ...

  10. 为什么 Java 中 CMS 垃圾收集器在发生 Concurrent Mode Failure 时的 Full GC 是单线程的?

    为什么 Java 中 CMS 垃圾收集器在发生 Concurrent Mode Failure 时的 Full GC 是单线程的? 在 CMS(Concurrent Mark-Sweep)垃圾收集器中 ...