当前位置: 首页 > article >正文

Spring事务底层源码解析(二)

今天根据具体的业务代码场景去分析spring事务的源码流程,其中事务传播属性还是PROPAGATION_REQUIRED

首先看下第一种业务场景,testTransaction1方法上加了@Transactionl注解,注解中调用了insertTransactionOne(),insertTransactionTwo()方法,这两个方法上面也都加了@Transactionl注解

首先看testTransaction1()方法,上节已经说了会创建数据库连接connection并与ThreadLocal绑定,此时事务状态newTransaction=true,表示这是新开启的事务。这些流程上节已经看过了,再看方法transactionOne.insertTransactionOne(),这个时候同样要去开启事务执行到了getTransaction方法中从ThreadLocal中获取数据库连接,此时就会判断isExistingTransaction方法,明显是存在的,就会去执行handleExistingTransaction方法

handleExistingTransaction方法的主要功能:

1、如果当前被调用的方法insertTransactionOne的事务传播属性为PROPAGATION_NESTED,那么就会将testTransaction1方法开启的事务挂起。

2、如果当前被调用的方法insertTransactionOne的事务传播属性为PROPAGATION_REQUIRED,那么就会将newTransaction属性设置为false,这个留意后面提交事务的时候有使用,然后就会去调用insertTransactionOne的原始方法,可以看到当前insertTransactionOne跟testTransaction1是在同一个事务中了。

当testTransaction1()方法执行完毕进行commit方法的时候,这里因为status.isNewTransaction为false,所以并不会执行testTransaction1方法的提交操作。

同理,执行完insertTransactionOne方法之后,继续执行insertTransactionTwo方法,流程跟insertTransactionOne相同,当这两个方法执行完毕的时候,那么就又回到了testTransaction1方法,此时他的newTransaction为true,那么就会执行doCommit方法将事务进行提交。

第二种基本场景,testTransaction2方法中,假如insertTransactionOne方法执行成功,insertTransactionTwo方法抛出异常

抛出异常之后会进到completeTransactionAfterThrowing方法,这里会

由于testTransaction2方法的isNewTransaction为false,所以不会执行回滚动作,但是将connectionHolder的rollbackOnly属性设置为了true。

insertTransaction2抛出异常后回到了testTransaction方法的执行逻辑,此时会再次进入到completeTransactionAfterThrowing方法,由于testTransaction的事务状态newTransaction为true,那么最终会执行con.rollback方法进行事务回滚操作。所以rollbackOnly属性是事务回滚操作中比较重要的属性

第三种基本场景,事务传播属性依然是PROPAGATION_REQUIRED;insertTransactionOne方法执行成功,但是insertTransactionTwo抛出异常,但是此时异常被捕获到,那么此时insertTransactionOne插入的数据会回滚吗?答案是肯定的。

关键属性其实还是rollbackOnly,insertTransactionTwo由于抛出异常了,会调用completeTransactionAfterThrowing方法将rollbackOnly更新为true,然后异常抛出,那么此时testTransaction3中的方法捕获到异常。

此时回到了testTransaction3方法的执行流程,且newTransaction属性为true,那么就会正常执行commitTransactionAfterReturning方法,这个时候判断rollbackOnly为true

由于testTransaction3的当前事务状态newTransaction为true,此时事务回滚。

第四种基本场景,事务传播属性依然是PROPAGATION_REQUIRED;这种场景是insertTransactionOne方法正常执行,insertTransactionTwo方法执行报错但是内部将异常进行捕获,并没有抛出来,此时的结论是insertTransactionOne方法事务提交,insertTransactionTwo方法没有插入数据。

原因就是insertTransactionTwo方法自己将事务给捕获了,没有进入到spring中的catch方法里面,那就不会执行到completeTransactionAfterThrowing方法,因此也就不会将rollbackonly设置为true,因此testTransaction4方法正常提交。

以上就是事务传播属性还是PROPAGATION_REQUIRED的主要流程。

接下来看另外一场常见的事务传播机制PROPAGATION_REQUIRED_NEW(新建事务,如果当前存在事务,把当前事务挂起)

在开发过程中,经常会出现一个方法调用另外一个方法,那么这里就涉及到了多种场景,比如a()调用b():
1. a()和b()方法中的所有sql需要在同一个事务中吗?
2. a()和b()方法需要单独的事务吗?
3. a()需要在事务中执行,b()还需要在事务中执行吗?
4. 等等情况...
所以,这就要求Spring事务能支持上面各种场景,这就是Spring事务传播机制的由来。那Spring事务PROPAGATION_REQUIRED_NEW传播机制是如何实现的呢?假如方法a()在一个事务中执行,a中调用了b方法,调用b()方法时需要新开一个事务执行:
1. 首先,代理对象执行a()方法前,先利用事务管理器新建一个数据库连接a
2. 将数据库连接a的autocommit改为false
3. 把数据库连接a设置到ThreadLocal中
4. 执行a()方法中的sql
5. 执行a()方法过程中,调用了b()方法(注意用代理对象调用b()方法)
i. 代理对象执行b()方法前,判断出来了当前线程中已经存在一个数据库连接a了,表示当前线
程其实已经拥有一个Spring事务了,则进行挂起
ii. 挂起就是把ThreadLocal中的数据库连接a从ThreadLocal中移除,并放入一个挂起资源对象

iii. 挂起完成后,再次利用事务管理器新建一个数据库连接b
iv. 将数据库连接b的autocommit改为false
v. 把数据库连接b设置到ThreadLocal中
vi. 执行b()方法中的sql
vii. b()方法正常执行完,则从ThreadLocal中拿到数据库连接b进行提交
viii. 提交之后会恢复所挂起的数据库连接a,这里的恢复,其实只是把在挂起资源对象中所保存
的数据库连接a再次设置到ThreadLocal中
6. a()方法正常执行完,则从ThreadLocal中拿到数据库连接a进行提交
这个过程中最为核心的是:在执行某个方法时,判断当前是否已经存在一个事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象,如果存在则表示已经存在一个事务了。

方法a第一次执行的时候,正常开启事务,流程与PROPAGATION_REQUIRED是一样的,关键流程是执行方法b()的时候,判断是存在数据库连接对象的、那么就会执行到handleExistingTransaction方法,做事务挂起操作。

suspend方法就是把ThreadLocal中的数据库连接从ThreadLocal中移除,并放入一个挂起资源对象 中,最终返回的suspendedResources就是持有了上个方法事务的连接,调用b方法这个时候就会重新调用startTransaction方法开启新的事务,也就是会创新新的连接。

b()方法开启新事务有两个关注的点:

1、newTransaction属性为true,就说明这是一个新的事务,当b方法执行到提交的时候就会真正提交;如果传播属性是PROPAGATION_REQUIRED,此时b方法这个属性应该为false,就表名方法b没有开启新事务,复用a方法的事务,最终提交由a方法提交

2、新创建的DefaultTransactionStatus方法的suspendedResources不为空,就是刚刚持有a()方法数据库连接的挂起资源对象;如果如果传播属性是PROPAGATION_REQUIRED,这个suspendedResources就是null,因为不会有挂起操作。这个对象在执行完b方法事务提交后,恢复a方法事务的时候需要用到,因此需要拿到a方法的连接。

b方法事务提交流程就跟PROPAGATION_REQUIRED也没什么区别,不过会有一个挂起事务恢复的操作,执行完b方法的事务提交之后,会走到cleanupAfterCompletion方法,由于当前事务的suspendedResources不为空,就会执行resume方法

这里会将挂起资源的数据库连接与当前线程ThreadLocal做一个重新绑定的操作,然后继续去执行a()方法的业务,如果此时a()方法执行失败,那么不会回滚b方法的事务。

Spring事务强制回滚操作:

正常情况下,a()调用b()方法时,如果b()方法抛了异常,但是在a()方法捕获了,那么a()的事务还是会正常提交的,但是有的时候,我们捕获异常可能仅仅只是不把异常信息返回给客户端,而是为了返回一些更友好的错误信息,而这个时候,我们还是希望事务能回滚的,那这个时候就得告诉Spring把当前事务回滚掉,其实利用的还是rollbackonly属性,做法就是:

最后一种事务传播属性PROPAGATION_NESTED(如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作),这种传播属性比较简单,其实就是利用数据库连接对象,在事务中设置一个savepoint,后续可以只回滚到某个savepoint。


http://www.kler.cn/news/361885.html

相关文章:

  • 如何实现金蝶商品数据集成到电商系统的SKU
  • 监控易监测对象及指标之:JBoss 7.1.x中间件监控
  • mysql数据量分库分表
  • 【宠物空气净化器选购指南】希喂、米家、IAM、352 、霍尼韦尔测评
  • 十一、数据库配置
  • 微信小程序文本收起展开
  • 大数据分析案例-基于随机森林模型的机器学习工程师岗位薪资预测
  • SQLI LABS | Less-3 GET-Error based-Single quotes with twist-String
  • 11种经典时间序列预测方法:理论、Python实现与应用
  • Linux云计算 |【第五阶段】ARCHITECTURE-DAY4
  • LabVIEW水质监测系统
  • leetcode 3191. 使二进制数组全部等于 1 的最少操作次数 I 中等
  • 计算机的错误计算(一百三十一)
  • EasyOCR——超强超便捷的OCR开源算法介绍与文本检测模型CRAFT微调方法
  • 『完整代码』坐骑召唤
  • 【数据结构】顺序表与链表的区别和各自特点
  • 数据结构-贪心算法笔记
  • MAC电脑的JDK、MAVEN配置及IDEA激活
  • Vehicle Spy3.9如何新建工程—Setup network Database
  • 基于SpringBoot中药材进存销管理系统【附源码】
  • C++基础(10. map_set 的使用)
  • 深入探索路由算法的核心原理与应用
  • ESlint代码规范
  • 基于PHP的减脂轻食购物网站【附源码】
  • 注册_登录安全分析报告:宝马中国
  • Vue学习笔记 Class绑定 Style绑定 侦听器 表单输入绑定 模板引用 组件组成 组件嵌套关系