偶尔博客闲逛发现有人讨论这个问题(我自己没有遇到过),翻了几个帖子没有几个讲清楚的,自己测试下吧

测试类:

package com.web.service;import com.StudyApplication;
import com.web.service.i.TransactionalOuter;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StudyApplication.class)
public class TransactionalTest {@Autowiredprivate TransactionalOuter transactionalOuter;@Testpublic void testTransaction() {transactionalOuter.outerBusiness();}
}

外层业务类:

package com.web.service;import com.web.service.i.TransactionalInner;
import com.web.service.i.TransactionalOuter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** @author zhaochao53* @date 2020/12/14 10:31* @desc*/
@Service
public class TransactionalOuterImpl implements TransactionalOuter {@Autowiredprivate TransactionalInner transactionalInner;@Transactional(rollbackFor = Exception.class)@Overridepublic void outerBusiness() {try {transactionalInner.innerBusiness();} catch (Exception e) {System.out.println("outer invoke inner  error " + e.getMessage());}int i = 1 / 1;}
}

内层业务类:

package com.web.service;import com.web.service.i.TransactionalInner;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** @author zhaochao53* @date 2020/12/14 10:31* @desc*/
@Service
public class TransactionalInnerImpl implements TransactionalInner {@Transactional(rollbackFor = Exception.class)@Overridepublic void innerBusiness() {int i = 1 / 0;}
}

在这种情况“同样Propagation.REQUIRED事务传播行为下,外部业务方法调用内部业务方法且对内部业务方法进行了try...catch”的时候会爆出这个错误 Transaction rolled back because it has been marked as rollback-only,为什么呢?

跟进源码:

在执行transactionalInner.innerBusiness()报错的时候,会进入经过

> org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

> org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing

> org.springframework.transaction.PlatformTransactionManager#rollback

> org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback方法执行doSetRollbackOnly(status)方法

    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 transactionif (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 earlyif (!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 markerif (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");}}finally {cleanupAfterCompletion(status);}}

最终调用到org.springframework.transaction.support.ResourceHolderSupport#setRollbackOnly方法将rollbackOnly置为true

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

然后执行到外部方法,异常被catch住,外部outer任务没有异常,继续执行业务方法后,进行事物提交,调用到org.springframework.transaction.support.AbstractPlatformTransactionManager#commit方法

 /*** 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*/@Overridepublic 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;}// 这里由于上面已经设置了rollbackOnly为true,会调用进入下面代码块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);}

会调用processRollback(defStatus, true)方法,入参的unexpectedRollback值为true,但是当前的事物已经被设置为应该进行回滚,所以会报UnexpectedRollbackException异常。

原因:

Propagation.REQUIRED事务传播行为下,inner与outer使用同一个事物,inner报错的时候已经将标记rollbackOnly置为true,outer在进行commit的时候才会报错。

解决方法:

inner视业务场景修改为Propagation.REQUIRES_NEW 或 inner不加事物 或 outer在catch到exception之后打印日志在throw出去 或 outer不进行try...catch均可!

关于spring事务传播行为引发的Transaction rolled back because it has been marked as rollback-only相关推荐

  1. spring 事务传播REQUIRED 与 NESTED的区别

    总结 NESTED 似乎与REQUIRED 是一样的,但是他们是不同的. 若a 调用b 方法.a方法为REQUIRED,且在a中捕获b方法异常.注意 a ,b 方法不要在一个service中,不然事务 ...

  2. spring上下文是什么意思_Java程序员只会CRUD连Spring事务传播机制都不懂?

    AQS到底有什么用?难道就真的只是为了面试吗? 当然不是说AQS没用,如果你不是做基础架构或者中间件开发,你很难感受到AQS的威力.当然,学习很多时候,需要的是正向反馈,学了太多造火箭的东西,面试完就 ...

  3. 原创 | CRUD更要知道的Spring事务传播机制

    来自:肥朝 AQS到底有什么用?难道就真的只是为了面试吗? 当然不是说AQS没用,如果你不是做基础架构或者中间件开发,你很难感受到AQS的威力.当然,学习很多时候,需要的是正向反馈,学了太多造火箭的东 ...

  4. Spring事务传播特性实例解析

    背景介绍 目前系统正在进行代码重构前期预研工作,目标采用spring控制事务以减少开发代码量,提高开发效率.同时避免开发人员编码控制事务所带来的链接没有释放,事务没有提交,出现异常事务没有回滚的Bug ...

  5. Spring事务传播行为详解

    前言 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为.事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为.这是Spring ...

  6. 太难了~面试官让我结合案例讲讲自己对Spring事务传播行为的理解!

    摘要: 原创出处 sf.gg/a/1190000013341344 「handaqiang」欢迎转载,保留摘要,谢谢! 前言 基础概念 1. 什么是事务传播行为? 2. Spring 中七种事务传播行 ...

  7. Spring 事务传播原理及数据库事务操作原理

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 先看看 Spring 事务的基础配置 <beans xmlns="http://www.springframework.org/ ...

  8. 《Spring事务传播行为详解》经典例子 看完这篇,别的不用看了

    前言 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为.事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为.这是Spring ...

  9. Spring事务传播性(较详细描述)

    Spring事务传播机制 前言 事务概述 事务传播性 传播性概述 策略详解 Propagation.REQUIRED Propagation.REQUIRED_NEW Propagation.NEST ...

最新文章

  1. 设计模式-Strategy模式
  2. 解决GetManifestResourceNames()无法读取资源文件
  3. metinfo mysql off_利用Sqlmap测试MetInfo企业网站管理系统MySql注入
  4. 了解架构设计远远不够!一文拆解 Tomcat 高并发原理与性能调优
  5. 正在爆发的互联网革命
  6. java--小示例:-3-可以作为测试内容使用
  7. python输出小数_python输出小数
  8. Embarcadero.ERStudio安装
  9. 微信群发红包原理 计算机,微信红包实现原理探讨
  10. PHP支付宝手机网站支付notify异步通知
  11. 程序员必备Java API和类搜索辅助工具-Jadeite和Apatite
  12. ctfshow--ssrf
  13. 转贴:求真功诚访大成名家--程立华 (2006-09-22 16:08:11)
  14. VS2022安装失败
  15. Error in nextTick TypeError Cannot read property 'children' of undefined 解决
  16. 什么是SYN Flood?
  17. seata 1.5.2 保姆级教程
  18. 乐华v56与v59的区别_乐华V59-5键通用程序(通用板)驱动程序
  19. 2021最新CSDN积分获取攻略(如何快速获取CSDN积分)
  20. yum和dnf安装安全更新

热门文章

  1. 用vue.js在页面输入古诗
  2. 怎么成为大公司的前端(例如:阿里巴巴)
  3. SolidEdge 如何绘制剖视图
  4. win7共享打印机错误,0x80070002
  5. Android中控制屏幕旋转的相关设置
  6. su和su - 的区别
  7. excel改变列的位置
  8. IDEA的Maven插件介绍
  9. 一体化办公平台,办公效率就一个字:高
  10. 600多个微信小程序源码_微信小程序在线音乐播放器及源码下载