脱离 Spring 实现复杂嵌套事务,之九(整合七种传播行为)

原创
2014/02/19 13:59
阅读数 3.8K

    本文是<实现 Spring 的事务控制>系列文章中一篇。本文假设读者已经阅读并理解《实现 Spring 的事务控制,之一(必要的概念)》文中所涉及的概念(当前连接引用计数),以及数据库连接的(new状态

工作流程详解

    如果您对本文每一次都提供给您的工作原理图还记忆犹新的话,下面这张图就是综合了上述所有事务控制行为之后的原理图。下一篇文章(实现篇),会根据这张原理图来告诉大家如何实现 Spring 那样的事务管理器。

    在开始工作流程讲解之前先对下面图中的一些特殊地方进行说明:

  • 1.“挂起当前事务”和“获取连接”是属于不同的工作但是成对出现的。在实现的时候它们也会被捏在一起。
  • 2.图中没有标识出来的事务行为是从 Behavior 判断节点之后无特殊处理的,故而在图中直接省略。
  • 3.状态的开始表示事务活动的开始。状态的结束表示事务活动的结束。

    我打算从状态入手来讲解上面这张图,正好可以温习一下已经提到的一些数据库状态或特征。

引用计数

    引用计数是指对某个数据库连接持有的数量。当需要使用数据库连接的时候,我们通常会从数据库连接池中申请一个连接出来。然后使用这个连接,接下来在销毁这个连接。

    在这其中申请和销毁,就用引用计数来表示了。每当申请就引用计数+1,当释放就引用计数-1。

    这种思想非常有用,特别是在那些对某个可重复分配的资源。数据库连接算是其中一种。

“new”特征

    new特征,指的是当事务管理器创建事务状态的时候,当前数据库连接是否存在事务。如果不存在事务就具备new特征。

    new特征,强调了某个时间点下当前数据库连接的状态。这个状态值可以协助事务管理器当执行 commit /rollback 的时候是否真的去执行它们。

    比方说:一个具备new特征的数据库连接,由于在事务管理器创建事务之前它并不存在事务。因此新创建的事务就需要事务管理器去管理。当遇到 commit/rollback 时候事务管理器就要负责这个事务的递交。

“suspend”特征

    supend特征是一个特殊的特征,它只存在于事务管理器上。这个特征用以表示当前数据库操作的底层是否有被挂起的数据库连接。

    产生这种状态是由于,当前数据库连接因为某种情况不能被释放。而此时还需要一个全新的数据库连接充当当前连接而产生的。在事务管理器中、REQUIRES_NEW 和 NOE_SUPPORIED 两个行为会产生这个效果。

    值得注意的是,要想产生 suspend状态。当前的数据库连接必须是开启了事务的,这就是上面提到的“某种情况”。我们知道满足 new 的条件是当前连接没有事务,而满足 suspend 的条件是当前连接有事务。因此我们可以看作 new 和 suspend 两个状态彼此互斥。这也可以简单的称(“new”与“suspend”的互斥性。

“savepoint ”特征

    如果事务管理器在创建事务的时候使用 Savepoint 用于分割上一个事务,那么新创建的事务状态就具备了 savepoint 特征。该特征只会发生于于 NESTED 行为中。

    然而并不是任何情况下 NESTED 行为都会产生 savepoint 状态,满足创建 savepoint 的条件是当前数据库连接已经开启了事务。

    在 commit / rollback 的情况下,凡是具有保存点的事务状态,无论它是否具备 new 或者 suspend 特征都是优先处理 savepoint 的。在实际情况中 savepoint 状态和“new”、“suspend”之间也存在着排斥。

    细想一下要想产生“savepoint”首先当前连接必须具有事务,这一点也就明确指出了与 new 特征的排斥。其次 suspend 状态只会存在于 REQUIRES_NEW 和 NOE_SUPPORIED 两个行为中。产生 savepoint 状态的行为不包括在其中。因此与 suspend 也是排斥的。

“rollBack”状态

    rollBack状态是在前面文章中未提及的状态,该状态的满足条件完全看开发者意愿。事务管理器只会在 commit 操作时判断正在执行递交操作的事务状态中是否包含 rollBack 状态,如果包含则 commit 操作会转换为 rollBack 操作。

   该状态是开发者从事务管理器创建事务状态之后,通过事务状态对象的方法设置上去的。与此相当的还有一个 readOnly 状态,在这里就不在单独去说了。

处理逻辑

1.通用的处理逻辑

    事务的操作有一个固定的流程如下:

  • 申请连接 -> 执行操作 -> cleanupAfter

    其中申请连接的时候会使引用计数+1,在cleanupAfter阶段会将申请的连接释放掉这会使引用计数-1。事务管理器会通过这样一个简单的闭环来工作。

2.PROPAGATION_REQUIRED(加入已有事务)
“尝试加入已经存在的事务中,如果没有则开启一个新的事务。”

    由于通用处理逻辑下并没有指定是否开启事务。因此在执行 申请连接 操作之后,申请的新的数据库连接存在两种状况(有事务、无事务)。

    该行为会判断是否存在事务,如果不存在则标记“new特征”同时开启事务。这样一来就可以保证 REQUIRED 行为下数据库操作是在一个事务中。

    最后由于在创建事务状态的时候可能会标记“new”状态,因此在事务递交和回滚的时候需要判断这个标记以处理正确的逻辑。

3.RROPAGATION_REQUIRES_NEW(独立事务)
“挂起当前存在的事务,并开启一个全新的事务,新事务与已存在的事务之间彼此没有关系。”

    REQUIRES_NEW 行为,强调了当存在事务的时候使用新的事务。因此,在 REQUIRED 基础上我们需要在有事务的情况下增加一个判断如果是 REQUIRES_NEW 行为那么就挂起数据库连接,然后重新申请一个连接并开启事务。

    在处理 commit 和 rollback 时只需要在 REQUIRED 处理完之后。判断一下是否存在挂起的事务,然后恢复一下挂起的事务即可。这里需要注意连接释放的问题。

4.PROPAGATION_NESTED(嵌套事务)
“在当前事务上开启一个子事务(Savepoint),如果递交主事务。那么连同子事务一同递交。如果递交子事务则保存点之前的所有事务都会被递交。”

    嵌套事务与独立事务在 begin 时。唯一的差别就是嵌套事务是 mark 了一个保存点,而独立事务使用的是挂起操作。

    在处理 commit 和 rollbakc 的时候需要先处理保存点,然后在接下来去处理接下来的情形。

5.PROPAGATION_SUPPORTS(跟随环境)
“是指 Spring 容器中如果当前没有事务存在,就以非事务方式执行;如果有,就使用当前事务。”

    最好处理的行为,只需要在申请和释放的时候处理好引用计数。其它事情一改不做即可。

6.PROPAGATION_NOT_SUPPORTED(非事务方式)
“是指如果存在事务则将这个事务挂起,并使用新的数据库连接。新的数据库连接不使用事务。”

    这个行为需要参照独立事务去实现。非事务强调的是当前连接不具备事务,由于不能影响已经存在的事务。因此需要挂起当前事务,不同于独立事务的是。新申请的数据库连接不需要开启事务。

    它的 commit 和 rollback 处理逻辑与独立事务一致。

7.PROPAGATION_NEVER(排除事务)
“当存在事务时抛出异常,否则就已非事务方式运行。”

    与前面不同,在处理 NEVER 行为时值需要判断是否存在事务。如果存在就抛出异常即可,至于后面的事情全部忽略把。在抛出的时候需要注意回收已经申请的连接资源。

8.PROPAGATION_MANDATORY(需要事务)
    如果不存在事务就抛出异常,否则就已事务方式运行。

    与 NEVER 逻辑相同,只是在没有事务的时候抛出异常即可。在抛出的时候需要注意回收已经申请的连接资源。

开启多个事务的问题

    下面就要讨论讨论同时开启多个事务时的问题了。

    首先设计事务传播属性的目的就是为了解决“A存在事务,B存在事务。在执行A的过程中还调用了B”这样的问题。

    在A调用B,切AB都有事务这种前提下。事务管理器要求能够保证 A,B 两个事务的正确性。比方说A在调用B时候,B不能贸然把A的事务关闭了。等等诸如此类的问题。

    另外作为事务管理器理应管理好事务处理的正确先后顺序。好比 A调用B,B调用C。而 C 不能贸然的把 A 的事务递交了而不考虑B的感受。

    我们先看第二个问题,事务处理要保证顺序。前面工作原理图中已经明确的展现了如何处理每种不同类型的事务。但是并没告诉我们如何保证事务的先后顺序。

    这块我们使用一个“先进后出”的数据结构来处理。每当开启事务的时候都往集合里 push。换据说说集合里位于栈顶的元素应当是,正在进行的那个事务。比方A调用B,B调用C,那么集合里自下而上应该是A,B,C三个元素,C位于栈顶。

    有了这一手数据,当我们在 C 方法中试图递交 A的事务时。我们就可以比较妥当的用一个预处理方法去处理,位于A之前的两个事务 B,C。

展开阅读全文
打赏
7
11 收藏
分享
加载中
哈库纳博主

引用来自“Kevin_Zhan”的评论

有办法实现例如,ServiceA结束后,执行ServiceB(不是ServiceA里面调用B),然后B出现异常,回滚了B,A没有,这种的要如何实现呢?
把事务挂到 A 和 B 上面,不要挂到 A和B 的外面。
2016/11/16 17:36
回复
举报
有办法实现例如,ServiceA结束后,执行ServiceB(不是ServiceA里面调用B),然后B出现异常,回滚了B,A没有,这种的要如何实现呢?
2016/11/16 11:20
回复
举报
解析的很到位,赞一个79
2016/07/21 16:08
回复
举报
更多评论
打赏
3 评论
11 收藏
7
分享
返回顶部
顶部