spring事务处理的设计与实现--传播级别的具体流程

问题

spring的事务处理为我们省掉了很多操作,如今只需要简单的配置一下就可以完成对应的事务操作。不过停留在会用的层面上还是缺少一些遇见问题时处理的能力。下面我要总结一下spring事务处理时的设计中,传播级别到底是如何工作的,比如

  1. REQUIRED为什么在try中catch住异常外层事务一样会回滚?
  2. REQUIRES_NEW是如何开启新事物,并与外层事务独立开来?
  3. NESTED是如何做到子事务不影响外层事务?外层事务会影响子事务?

名词

外层事务:事务A中调用了事务B,那么事务A就是B的外层事务。
新事务:事务A中调用事务B,事务B的传播级别为REQUIRES_NEW,那么事务B就是新事务。
子事务:事务A中调用事务B,事务B的传播级别为NESTED,那么事务B就是新事务。

探究

先把这里org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction作为事务处理的入口,来看一下里面的行为。

image.png

前三行是一些配置信息与调用的方法信息,不多讲。

这里的关键点在于if里面的内容:
1.获取TransactionInfo信息,可能会新建事务。createTransactionIfNecessary
2.执行事务代码invocation.proceedWithInvocation();
3.发生异常的时候的处理completeTransactionAfterThrowing(txInfo, ex);
4.无论有无异常,都会清理当前的事务信息。cleanupTransactionInfo(txInfo);
5.正常执行后的提交动作。commitTransactionAfterReturning(txInfo);这里的提交并不保证完成提交,是问题1,3的关键。

先介绍几个关键的元信息类:
TransactionDefinition:就是被@Transaction中的一些属性信息,包含传播级别,隔离级别,超时时间,只读设置,名字。

TransactionAttribute:继承了TransactionDefinition,包含了qualifier,rollbackOn异常回滚的支持信息。

DefaultTransactionStatus

image.png

transaction:事务对象,每一个事务方法都可能有自己独立的对象,也可能公用一个。
newTransaction:是否是新事务,用来影响事务的提交和回滚。
newSynchronization: 新的同步器?没找到一个很好的解释,用来控制当前线程与事务属性的关系。TransactionSynchronizationManager就是这个关系的管理器。
readOnly: 只读属性,这里不关心。
debug:就是一个debug的开启缓存,省去二次计算。
suspendedResources:挂起的资源,比如REQUIRES_NEW需要挂起外层事务。

TransactionInfo:事务的信息概括,包含上面的TransactionAttribute,TransactionStatus,和事务管理器对象,方法信息,重要的是oldTransactionInfo外层事务信息,在事务结束后会恢复执行外层的事务逻辑

回到方法TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

image.png

在449行用了一个委托的事务属性对象来描述TransactionAttribute。就是关于事务方法的描述信息。
再来看461的getTransaction,这个过程就是对不同的传播级别的处理方式。我们这里只关注DefaultTransactionStatus的变化。

image.png

可见这里处理的有存在事务与不存在事务的两种处理流程。
isExistingTransaction通过实现可以看到就是数据库连接是否打开了事务。
先看在新建事务时的过程:

  1. 358行,传播级别为PROPAGATION_MANDATORY时,抛出异常,这就是这个传播级别必须要在事务方法中调用的原因。
  2. 362行,对于REQUIRED,REQUIRES_NEW,NESTED级别的处理。重点关注DefaultTransactionStatus里面的newTransaction和newSynchronization
    370行,在默认情况下newSynchronization为true,因为getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS
    371行,newTransactionStatus方法中,再用
    boolean actualNewSynchronization = newSynchronization &&!TransactionSynchronizationManager.isSynchronizationActive();
    来决定最终的newSynchronization。

TransactionSynchronizationManager.isSynchronizationActive()就是获取当前现成的事务同步器的激活状态。如果已经激活,就不用重复激活了。
由此可见,最终的newSynchronization是和当前的同步器激活状态关系的。只要是已经激活,那么就是false了。

373行,doBegin就是开启了事务连接的事务状态。
374行,prepareSynchronization准备同步器的初始化。进入方法里面可以看见是设置了当前线程的:
1.激活状态。2.隔离级别。3.只读。4.事务名称。5.空的同步器集合(TransactionSynchronization)。

这里我把几种级别的各个参数流程列一下,以便讲解:


image.png

1.REQUIRED:
在没有外层事务时,它需要一个事务管理器transaction,此时的事务同步器还没有激活,因此需要激活当前线程的事务同步器即actualNewSynchonization为true,同时调用了doBegin来让具体的实现打开线程状态。最后准备并初始化线程同步器的状态prepareSynchronization,即设置了当前线程事务的信息,并激活了同步器状态initSynchronization
在有外层事务时,它不需要挂起事务资源,因为要加入到当前的事务。事务同步器与外层用同一个。由于不会挂起资源,也就不会重置同步器的状态,也不需要重新激活同步器。这样就加入到当前的事务中。
之后在调用事务方法的时候,如果出现了异常,即使被外层捕获到不抛出,也会使外层事务回滚。这里我们就要看一下completeTransactionAfterThrowing这个方法里面的东西。

completeTransactionAfterThrowing.png

很简单,在事务有效的时候,符合需要回滚的异常时,走rollback方法。否则继续commit操作。
再来深入一下rollback方法,里面调用了processRollback,
image.png

这里走的是第三个if分支,两个条件,1.TransactionStatus的rollbackOnly状态;2.全局的rollbackOnly状态isGlobalRollbackOnParticipationFailure,默认返回true,表示调用失败时候,需要全局回滚。之后进入到doSetRollbackOnly,其实就是让各自的实现去设置连接的rollbackOnly状态。
之后在外层事务提交的时候,具体看commit方法。
image.png

我们看见两个分支,
defStatus.isLocalRollbackOnly()只回滚本事务

!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly(),shouldCommitOnGlobalRollbackOnly表示全局事务在标记rollbackOnly时候是否需要继续提交,默认返回false,只有JTa的实现返回了True。defStatus.isGlobalRollbackOnly()是获取transaction的rollbackOnly标记,应该是标记在对应的连接状态上。
由上面发现已经将这个transaction标记了rollbackOnly,所以就转向了去处理回滚的操作。processRollback->doRollback。

描述有点多,总结一下就是:

1.REQUIRED参与到当前的事务,融合进来。
2.当它出现异常时,会标记当前的transaction管理器中连接的rollbackOnly状态,使得外层事务在提交的时候监测到这个标记,发现不可以继续提交,需要回滚。

2.REQUIRES_NEW:
在没有外层事务时,情况同REQUIRED,就是打开一个事务,激活同步器等操作。
在有外层事务时,它是需要挂起事务资源的,挂起资源时,会将同步器的状态恢复为初始状态,通知具体实现进行相应的挂起资源操作。这个时候此事务的同步器状态为true,所以会准备一个新的事务同步器状态,开启新事务doBegin。
同1理来看,这个新事物在processRollback中不会标记transaction管理器的rollbackOnly状态,只是触发了自己的doRollback操作,所以不会影响到外层事务的提交。

3.NESTED:
在没有外层事务时,情况同REQUIRED,就是打开一个事务,激活同步器等操作。
在有外层事务时,它不会挂起事务资源,这里讨论有支持savePoint的实现(除了Jta)。不会重置同步器,也就是说与外层事务共用一个同步器。与REQUIRED类似,与外层事务进行了融合。只不过在发生异常的时候,在processRollback走了status.hasSavepoint()分支,这样就优先进入了status.rollbackToHeldSavepoint()里面。


image.png

然后通过对应的实现通知连接来回滚到某个savePoint,并且释放掉。
这样就完成了单独的回滚操作,并不会向REQUIRED那样影响到外层的提交。
以上我们三个问题解答完了。

4.MANDATORY:
没有外层事务时抛出异常,必须要在事务里面执行。
有外层事务时,像REQUIRED那样融合进去。

5.SUPPORTS:
没有外层事务时,不会使用transaction管理器,也就不会形成事务操作。
有外层事务时,像REQUIRED那样融合进去。

6.NOT_SUPPORTED:
没有外层事务时,以非事务方式运行。
有外层事务时,把外层事务挂起,但是不会为自己新建事务,以非事务状态运行。

7.NEVER:
没有外层事务时,以非事务方式运行。
有外层事务时,抛出一场。

总结:

以上就是把所有传播级别的具体流程简单分析了一下,让我们知道了这7中级别是如何工作的,也解决了REQUIRED,REQUIRES_NEW和NESTED里面对于异常是如何处理的。
1.REQUIRED在处理异常时是给对应的连接设置了rollbackOnly状态,来影响外层事务的提交的。外层事务发现了rollbackOnly为true,就会转向到回滚流程。
2.REQUIRES_NEW是一个单独的事务操作,回滚完自己的事务不会影响其他。
3.NESTED是融合在外层事务中的,只不过这里支持了savePoint的操作,使得子事务在回滚时会指定到一个savePoint上,不会影响外层事务。因为与外层事务融合了,所以外层事务会影响到子事务。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 什么是事务 事务就是一组操作数据库的动作集合。 动作集合被完整地执行,我们称该事务被提交。动作集合中的某一部分执行...
    超级变换形态阅读 670评论 0 6
  • 事务传播行为 什么叫事务传播行为?听起来挺高端的,其实很简单。 即然是传播,那么至少有两个东西,才可以发生传播。单...
    青青子衿zq阅读 2,487评论 0 2
  • 转载自:https://www.jianshu.com/p/8ddc01f23540 Spring事务机制主要包括...
    星海辰光大人阅读 345评论 0 0
  • 分享日志第一百一十四天10.3 让分享成为一种习惯!读经 遍 累计 56遍 今天与同伴做了个小游戏,是儿时玩过的互...
    孝文4931阅读 157评论 0 1
  • 今天带孩子去看马戏团表演,孩子们很兴奋,我却不太喜欢看这些。 不爱看并不是说节目不精彩或其他原因,而是我每次看到驯...
    无聊啊哈哈阅读 74评论 0 0