天天看点

Spring 闯关指南:事务管理

暂且抛开 Spring 提供的事务管理模块。我们自己来思考下,仅仅基于 spring 基础框架来实现一个事务管理究竟有多复杂?

  • 首先为了不侵入业务代码,我们会选择使用代理来完成事务管理。
  • 其次,使用了代理之后,我们必须确保代理对象和目标对象获取到的是同一个连接。
  • 然后,即使获取到同一个连接,如果在业务逻辑中,调用了其它也需要事务支持的方法,此时,又该做何处理呢?

我能想到的问题有这些,但真相往往比我们所看见的更复杂,talk is cheap,show me the code ,大家都知道的。

第一点谈到为了不侵入业务代码,选择使用代理完成事务管理。这是正确的方向,但问题是我们不可能通过编程的方式单独去为每一个对象创建代理对象。所以,我们需要将需要事务支持的方法模块化,以此来创建代理对象,而这恰好也是 AOP 的领域。

如果思考到上面这一点,我们便可以基于 AOP 将事务管理复杂的过程抽象成解决 AOP 问题的通用过程。回到 Spring 的事务管理上来,现在,我们只需要分而治之,找到切入点和建议,即可分析清楚。

结合以往的使用经验,常用的事务管理都是基于方法的,所以切入点应该是切入点接口

StaticMethodMatcherPointcut

的实现类

TransactionAttributeSourcePointcut

。注意,这里的

static

指的应该是匹配与方法的运行时入参无关。

该切入点需要一个

TransactionAttributeSource

实例,该实例是用于元数据检索的策略接口,它的实现类需要从我们的配置中获取到事务属性。如果单从数据这一方面来分析整个过程,这里相当于数据流入过程。

TransactionAttributeSource

存储了静态的配置数据,所以,在事务管理的整个过程中,它是必不可少的。

切入点梳理完了,现在回到建议,即 Spring 中的拦截器。查看

MethodInterceptor

接口的实现类,找到了

TransactionInterceptor

类。可以发现该类仍然需要一个

TransactionAttributeSource

。所以这里的实例是和上述切点中的实例应该是同一个。切点中负责收集配置数据,拦截器中负责使用。所以单从数据这一方面来看,这里相当于数据处理过程。

拦截器中最重要的方法便是

invoke

,下面将详细介绍

TransactionInterceptor

拦截器中的该方法,这便是整个事务管理的核心过程所在了。

invoke

方法:

  1. 找到

    TransactionAttribute

    ;
  2. 找到

    TransactionManager

    ;
  3. 创建

    TransactionInfo

    ;
  4. 执行目标对象方法;
  5. 抛出异常时的

    TransactionInfo

    处理;
  6. 最终清理

    TransactionInfo

    ;
  7. 正常场返回时的

    TransactionInfo

    处理;
  8. 返回结果;

大道至简,衍化至繁,从上面仍然能够看见自己处理事务时的影子,所以理解这个过程不难,但其中的一些技术,我们仍需细细琢磨。

第一步的

TransactionAttribute

是由前文提到的

TransactionAttributeSource

提供。主要是事务的数据模型;

第二步的

TransactionManager

则是操作事务的,用于执行回滚,提交等操作;Spring 的事务管理并未提供真正的事务管理实现,而是选择在更高层次进行了封装,将具体的实现交给了能够支持事务管理执行回滚等操作的其它外部组件。

第三步的

TransactionInfo

保存了事务相关的信息,像

PlatformTransactionManager

TransactionAttribute

TransactionStatus

等。

第四步则是执行目标对象的方法,不过这并不意味着执行的是业务逻辑方法,因为可能存在代理嵌套。

第五步则是在目标对象方法抛出异常时所做的处理,最终这个异常会被抛出,但无可怀疑,这里的处理需要进行回滚;

第六步是清理操作,无论异常是否抛出,都会执行该操作,我们可以将此步理解为释放资源。

第七步则是目标对象方法正常执行之后所做的工作,当然,这工作中肯定会包含提交事务。

第八步则是正常返回了,整个过程结束。

这其中的第三步,准备

TransactionInfo

则是做了很多的工作。就像在文章开头提到的最后两个问题:

  • 使用了代理之后,我们需要确保代理对象和目标对象获取到的是同一个连接。
  • 即使获取到同一个连接,如果在业务逻辑中,调用了其它也需要事务支持的方法,此时,又该做何处理?

首先,对于第一个问题,先假设我们使用的是

mybatis

作为持久层框架,那么我们需要获取连接的地方是在开启事务时,还有业务代码以及事务结束时。那么如何保证获取到的都是同一个连接呢?

这就与

TransactionSynchronizationManager

有关了,它能在线程级别同步资源,保证同一个线程使用的都是相同的资源。

mybatis

并没有从数据源直接获取连接,而是将数据源传递给

DataSourceUtils

工具类来获取连接。该工具类内部会去处理线程级资源同步的问题。

第二个问题,我想你已经明白我指的是事务传播行为。当事务方法 A 调用事务方法 B 时,方法 B 是继续在调用者 A 的事务中运行呢?还是自己重新开启一个事务?事务传播行为基于以上两个区分重新划分出了七种类型:

  1. PROPAGATION_REQUIRED

    : 表示当前方法必须在事务中,如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新事务。
  2. PROPAGATION_SUPPORTS

    : 表示当前方法不需要事务,但如果存在事务的话,那么该方法会在事务中运行。
  3. PROPAGATION_MANDATORY

    : 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
  4. PROPAGATION_REQUIRES_NEW

    : 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。
  5. PROPAGATION_NOT_SUPPORTED

    : 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。
  6. PROPAGATION_NEVER

    : 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。
  7. PROPAGATION_NESTED

    : 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与

    PROPAGATION_REQUIRED

    一样。

事务传播行为在创建

TransactionStatus

对象的过程中完成检查计算。

那么文中提到的挂起操作是如何实现的呢?既然有挂起也就有恢复,这其实是一种新老资源的临时交接,在挂起当前资源时,需要一个新的存储对象来“存档”当前资源。然后,将这个新的存储对象交接给新的资源对象,然后在新的资源对象结束时,老的资源对象便可以被取出恢复了。这整个挂起恢复的流程定义在

TransactionManager

类中。而具体的操作,例如需要释放某些真实的资源则交由

TransactionSynchronizationManager

来维护,恢复操作也是如此。设计模式中有一原则叫单一职责,我想,Spring 将这一原则发挥到了极致。

invoke

方法中后续的几步,就像标准的事务 commit/rollback 一样,这些具体操作由

TransactionManager

的具体实现类完成。

整个事务的基本流程分析就是这样了。如果想对某一方面深入了解,我想,基于你的经验,再加上上文,你也能很快地找到入口。

最后,再试着分析下日常处理事务时,经常遇到的两个问题:

  • 事务注解注解需要指定

    rollbackFor

    的值;
  • 私有方法上添加事务注解无效;

rollbackFor

属性是用于在业务代码执行期间,如果抛出异常,会根据该属性指定的异常和抛出的异常来判断是否回滚。这一步在

invoke

方法的第五步:抛出异常时的

TransactionInfo

这一步处理。具体的判断操作则由事务配置类

TransactionAttribute

接口的实现类判断;

可如果我们未指定该属性值,却会对抛出的异常是

RuntimeException

Error

子类的方法进行回滚。这是因为

TransactionAttribute

的默认实现类

DefaultTransactionAttribute

实现了这个处理逻辑。而在 Spring 中使用的多是继承于

DefaultTransactionAttribute

的子类,当子类实现了自己的异常判断逻辑后,如果无法匹配上,则会回调父类的判断方法。归根结底,这也就是为什么我们需要特地指定回滚支持的异常类型。

第二个问题,为什么私有方法上,添加事务注解又无效呢?这其实与代理机制有关。不仅仅是私有方法,受保护的方法也一样。反映到代码上,在

TransactionAttributeSourcePointcut

切点中会基于其持有的

AnnotationTransactionAttributeSource

实例的

publicMethodsOnly

属性判断,而这个属性默认情况下是 true 的。所以,对于私有方法也就不支持了。

整个事务管理分析就到这里。本文没有区分段落的原因,是想试试自己是否能够清晰流畅地描述整个过程。如果你能看到这里,我想我的目的已经实现了。当然,要说你是直接划到底部的,那我可得回复你一句:“文章内容我已帮你看过啦,可赞,不要犹豫啦!【/手动狗头.jpg】”

这是 Spring 闯关指南系列 的第四篇文章,如果你觉得我的文章还不错,并对后续文章感兴趣的话,可以通过扫描下方二维码关注我的公众号。谢谢!

Spring 闯关指南:事务管理