Spring嵌套事务是怎么回滚的?
- 事务的传播机制
- 多数据源的切换问题
更深入理解 Spring 事务。
用户注册完成后,需要给该用户登记一门PUA必修课,并更新该门课的登记用户数。
为此,我添加了两个表。
课程表 course,记录课程名称和注册的用户数。
用户选课表 user_course,记录用户表 user 和课程表 course 之间的多对多关联。
同时为课程表初始化了一条课程信息
接下来我们完成用户的相关操作,主要包括两部分:
- 新增用户选课记录
- 课程登记学生数 + 1
新增业务类 CourseService实现相关业务逻辑,分别调用了上述方法保存用户与课程的关联关系,并给课程注册人数+1
为避免注册课程的业务异常导致用户信息无法保存,这里 catch 注册课程方法中抛出的异常。希望当注册课程发生错误时,只回滚注册课程部分,保证用户信息依然正常。
为验证异常是否符合预期,在 regCourse() 里抛一个注册失败异常:
执行代码:
注册失败部分的异常符合预期,但是后面又多了一个这样的错误提示:Transaction rolled back because it has been marked as rollback-only
最后用户和选课的信息都被回滚了,显然这不符预期。
期待结果是即便内部事务regCourse()发生异常,外部事务saveStudent()俘获该异常后,内部事务应自行回滚,不影响外部事务。
这是什么原因造成的呢?
源码解析
伪代码梳理整个事务的结构:
整个业务包含2层事务:
- 外层 saveUser() 的事务
- 内层 regCourse() 事务
Spring声明式事务中的propagation属性,表示对这些方法使用怎样的事务,即:
一个带事务的方法调用了另一个带事务的方法,被调用的方法它怎么处理自己事务和调用方法事务之间的关系。
propagation 有7种配置:
- REQUIRED
默认值,如果本来有事务,则加入该事务,如果没有事务,则创建新的事务。 - SUPPORTS
- MANDATORY
- REQUIRES_NEW
- NOT_SUPPORTED
- NEVER
- NESTED
因为:
- 在 saveUser() 上声明了一个外部的事务,就已经存在一个事务了
- 在propagation值为默认REQUIRED时
regCourse() 就会加入到已有的事务中,两个方法共用一个事务。
Spring 事务处理的核心:
TransactionAspectSupport.invokeWithinTransaction()
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {TransactionAttributeSource tas = getTransactionAttributeSource();final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);final PlatformTransactionManager tm = determineTransactionManager(txAttr);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// 是否需要创建一个事务TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// 调用具体的业务方法retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// 当发生异常时进行处理completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}// 正常返回时提交事务commitTransactionAfterReturning(txInfo);return retVal;}//......省略非关键代码.....
}
整个方法完成了事务的一整套处理逻辑,如下:
- 检查是否需要创建事务
- 调用具体的业务方法进行处理
- 提交事务
- 处理异常
当前案例是两个事务嵌套,外层事务 saveUser()和内层事务 regCourse(),每个事务都会调用到这个方法。所以,该方法会被调两次。
内层事务
当捕获了异常,会调用
TransactionAspectSupport.completeTransactionAfterThrowing()
进行异常处理:
对异常类型做了一些检查,当符合声明中的定义后,执行具体的 rollback 操作,这个操作是通过如下方法完成:
AbstractPlatformTransactionManager
rollback()
该回滚实现负责处理正参与到已有事务集的事务。委托执行Rollback和doSetRollbackOnly。
继续调用
processRollback()
该方法里区分了三种场景:
- 是否有保存点
- 是否为一个新的事务
- 是否处于一个更大的事务中
因为默认传播类型REQUIRED,嵌套的事务并未开启一个新事务,所以属于当前事务处于一个更大事务中,所以会走到分支1。
如下的判断条件确定是否设置为仅回滚:
if (status.isLocalRollbackOnly() ||isGlobalRollbackOnParticipationFailure())
满足任一,都会执行 doSetRollbackOnly():
- isLocalRollbackOnly
默认 false,当前场景为 false - isGlobalRollbackOnParticipationFailure()
所以,就只由该方法来确定了,默认值为 true, 即是否回滚交由外层事务统一决定
条件得到满足,执行
DataSourceTransactionManager#doSetRollbackOnly
最终调用
DataSourceTransactionObject#setRollbackOnly()
内层事务操作执行完毕。
外层事务
外层事务中,业务代码就捕获了内层所抛异常,所以该异常不会继续往上抛,最后的事务会在 TransactionAspectSupport.invokeWithinTransaction()
中的
TransactionAspectSupport#commitTransactionAfterReturning()
该方法里执行了commit 操作:
AbstractPlatformTransactionManager#commit
当满足 !shouldCommitOnGlobalRollbackOnly() &&defStatus.isGlobalRollbackOnly()
,就会回滚,否则继续提交事务:
- shouldCommitOnGlobalRollbackOnly()
若发现事务被标记了全局回滚,且在发生全局回滚时,判断是否应该提交事务,这个方法的默认返回 false,这里无需关注 - isGlobalRollbackOnly()
该方法最终进入
DataSourceTransactionObject#isRollbackOnly()
之前内部事务处理最终调用到DataSourceTransactionObject#setRollbackOnly()
public void setRollbackOnly() {getConnectionHolder().setRollbackOnly();
}
- isRollbackOnly()
- setRollbackOnly()
两个方法本质都是对ConnectionHolder.rollbackOnly
属性标志位的存取
但ConnectionHolder则存在于DefaultTransactionStatus#transaction属性。
综上:外层事务是否回滚的关键,最终取决于DataSourceTransactionObject#isRollbackOnly(),该方法返回值正是在内层异常时设置的。
所以最终外层事务也被回滚,从而在控制台中打印上述日志。
这就明白了,Spring默认事务传播属性为REQUIRED:若已有事务,则加入该事务,若无事务,则创建新事务,因而内外两层事务都处于同一事务。
在 regCourse()中抛异常,并触发回滚操作时,这个回滚会继续传播,从而把 saveUser() 也回滚,最终整个事务都被回滚!
修正
Spring事务默认传播属性 REQUIRED,在整个事务的调用链上,任一环节抛异常都会导致全局回滚。
所以只需将传播属性改成 REQUIRES_NEW :
运行:
异常正常抛出,注册课程部分的数据没有保存,但用户还是正常注册成功。这意味着此时Spring 只对注册课程这部分的数据进行了回滚,并没有传播到外层:
- 当子事务声明为 Propagation.REQUIRES_NEW 时,在
TransactionAspectSupport.invokeWithinTransaction()
中调用createTransactionIfNecessary()
就会创建一个新的事务,独立于外层事务 - 而在 AbstractPlatformTransactionManager.processRollback() 进行 rollback 处理时,因为 status.isNewTransaction() 会因为它处于一个新的事务中而返回 true,所以它走入到了另一个分支,执行了 doRollback() 操作,让这个子事务单独回滚,不会影响到主事务。
Spring嵌套事务是怎么回滚的?相关推荐
- 详解spring事务失效和回滚失败的场景
详解spring事务失效和回滚失败的场景 详解spring事务失效和回滚失败的场景 前言 一 .事务不生效 1.访问权限问题 2. 方法用final修饰 3.方法的内部调用 3.1 新加一个Servi ...
- java 自定义异常 未回滚_抛出自定义异常,spring AOP事务不回滚的解决方案
spring AOP 默认对RuntimeException()异常或是其子类进行事务回滚,也就是说 事务回滚:throw new RuntimeException("xxxxxxxxxxx ...
- Spring中的事务回滚 网上比较不错的文章
1 浅谈Spring中的事务回滚 https://www.cnblogs.com/zeng1994/p/8257763.html 2 spring 事务回滚 https://www.cnblogs.c ...
- Spring Boot 整合——Spring batch重试和回滚
关于版本 依赖 版本 springboot 2.4.0 spring batch 2.4.0 代码地址 因为每个例子涉及代码较多,且包含测试用例,如果都贴到文章中内容过多,所以只贴出了部分代码.全部的 ...
- Spring : Spring @Transactional-嵌套事物回滚
1.美图 2.概述 事务回滚参考:Spring : Spring @Transactional-事物回滚 嵌套事务参考:Spring: 事务传播机制 3.前文回顾 // 2.如果当前已经存在事物 // ...
- Spring中@Transactional事务回滚(含实例详细讲解,附源码)
一.使用场景举例 在了解@Transactional怎么用之前我们必须要先知道@Transactional有什么用.下面举个栗子:比如一个部门里面有很多成员,这两者分别保存在部门表和成员表里面,在删除 ...
- spring诸如方式_回滚诸如在家工作之类的程序时,请谨慎操作
spring诸如方式 作为开放组织的宣传者和开放原则的大使,我充分意识到组织在试图实现持久变革时所面临的挑战. 改变根深蒂固的组织文化不应掉以轻心. 人们应该非常仔细地权衡,充分辩论,然后完全拥抱. ...
- 每日一博 - 常见的Spring事务失效事务不回滚案例集锦
文章目录 事务不生效 方法内部调用 修复方法一 : [新加一个Service方法] 修复方法二:[在该Service类中注入自己] 修复方法三:[通过AopContent类]<---- 推荐 访 ...
- Spring 事务提交回滚源码解析
2019独角兽企业重金招聘Python工程师标准>>> 前言 在上篇文章 Spring 事务初始化源码分析 中分析了 Spring 事务初始化的一个过程,当初始化完成后,Spring ...
最新文章
- pycharm git gitee 如何将红色文件推送到repository?(将工作区内容添加到暂存区)(git add)
- python单例类命名_单例模式(java/python/c++)
- 如何熟悉一个开源项目
- Visual Studio 的码云扩展 V1.0.85 发布
- CSS自定义文件上传按钮
- Verizon:2013年数据破坏调查报告(DBIR)【更新版】
- 鸿蒙os会给小米用吗,国内手机厂商是否支持鸿蒙?中兴正式回应!小米的态度很意外!...
- HCIA RS题库及解析(2018版)
- 试题 算法训练 印章
- 台湾Yahoo联手Mozilla 对抗Google
- 如何用Visio画出总线(空心的箭头)
- Pytorch Tutorial 学习笔记(六)模型部署
- 利用数据泵导入导出dmp
- 简易平滑轮播(纯CSS)
- 用“掩码位图“,制作类似.png的“透明图片“①
- vue中将html页面转为图片并且下载该图片
- 【算法】常见数据结构基本算法整理
- 华赛防火墙ipsec-***配置
- java 正切_Java tan()方法
- 多角度透彻理解渐近表示法(大O表示法)
热门文章
- 实验吧——天下武功唯快不破
- 你说五毛就五毛? 生产者消费者模式(Producer Consumer Pattern)来讲解
- CentOS7-搭建Ftp服务
- C++难学吗?为什么难学?如何去学?
- 下半年重要的10大美国写作比赛不要错过
- 萨斯病毒感染情况_审美萨斯1:建筑与风格组织
- php 点餐系统 毕业设计,php毕业设计_基于php的校园餐厅网上订餐系统
- Microsoft SQL Server 图书管理数据库的建立
- UIFont字体大全
- 室内空气流动原理图_新风系统工作原理图—新风系统工作原理介绍