前文

数据库事务想必大家已经非常熟悉,事务实际上就是DB的一个操作序列,在这个操作序列中,操作要全部完成,要么全部不完成。其中ACID四大特性估计大家已经滚瓜烂熟了。
在实际应用开发过程中,我们通常使用WEB框架+ORM框架来实现应用逻辑,例如Spring+MyBatis,而框架则为我们封装好了对数据库的操作,即使我们在业务方法中需要事务地完成一系列操作,最简单的方法也只需要在方法上注解@Transaction,框架底层为我们实现具体的事务开启、回滚和提交操作。这看上去非常美好,也没有什么问题,但作为一个开发人员,难道对他是如何实现没有一点好奇嘛?
本文将从Spring Transaction源码层面进行简单的分析,并且找出一些平时使用过程中可能没有留意的一些细节,解开框架下隐藏的秘密。
本文大纲如下:

大纲即本人阅读笔记,如感兴趣,可从https://github.com/Kwin1113/Xmind-Notes获取完整版~
Spring Transaction
Spring关于事务的代码大部分都在spring-tx模块下。说实话,Spring对事务支持的代码量也比较少,这一方面说明了Spring事务其实非常简单,另一方面也方便了我们对其原理进行一些简单的研究。
我们先从一个Java类关系图看一下其中几个关键点,下图大概分为三大部分,首先是事务属性部分(右半部分),通过注解和注解解析器(当然也可以通过xml配置,本文主要讲使用比较多的注解驱动);其次是事务同步器部分(左下部分),事务同步器作为回调接口,大量地参与了事务切面过程;最后是事务管理部分(左上部分),也是经典的切面拦截器实现(和Spring Cache有异曲同工之妙)。

事务属性
在具体深入了解之前,我们先简单了解一下第一部分的几个基础类。
@Transactional注解: 事务注解,标记类或方法上。标记事务的同时指定相关的事务参数。
TransactionDefinition:事务定义。定义了Spring规范的事务属性。
TransactionAttribute:事务参数。@Transactional注解中设置的事务参数。TransactionAttribute通过继承TransactionDefinition实现,并增加了回滚异常配置,因此该接口大致和TransactionDefinition相同。其主要区别在于TransactionAttribute偏向于用户配置,而TransactionDefinition属于事务本身的属性
TransactionAnnotationParser:事务注解解析器。Spring有默认实现类SringTransactionAnnotationParser,通过TransactionManagementConfiguration中配置TransactionAttributeSource指定事务注解解析器来解析拦截到的所有事务方法。
TransactionStatus:事务状态。标记事务当前的状态,事务管理器通过该对象进行事务的回滚或提交操作。
这些主要是在Spring事务中参与的一些基础概念,主要是对事务属性的描述。
除此之外,还有比较重要的概念就是TransactionManager和TransactionSynchronization,将会在下文详细介绍。
事务管理
可能有些同学在平时开发过程中,除了通过@Transactional注解这种声明式事务使用方式外,也使用过编程式事务,肯定接触过TransactionManager这个概念,该接口是标记接口,我们最常使用的应该是PlatformTransactionManager。

从接口定义方法上就能知道,它是Spring事务实现的关键接口,定义了获取事务、提交和回滚操作。
平时我们通过JDBC连接数据库时,其实就是通过其子类DataSourceTransactionManager实现DB底层的事务管理。在SpringBoot中,通过DataSourceTransactionManagerAutoConfiguration为我们进行了自动配置注入了该Bean。我们下文中默认都已DataSourceTransactionManager作为子类实现介绍。
在事务管理器的设计方面,作者Juergen Hoeller使用了模板方法设计模式,在PlatformTransactionManager的抽象子类AbstractPlatformTransactionManager中定义了Spring的标准事务流操作,而将具体的提交回滚操作委托给具体子类实现。因此Spring事务的扩展性非常强,标准事务流已经定义好了,如果需要扩展,用户只需要实现基于用户数据库的一套子类方法即可,实现了简单的可插拔的插件性配置。

图中红框内方法定义了标准的事务操作,将蓝框中的抽象模板方法委托给具体子类实现。
在详细讲这一块之前,先把剩下的一个TransactionSynchronization完成。
TransactionSynchronization是事务同步回调的接口,在Spring事务中,大量使用了该类,在事务各个状态阶段进行相关的回调操作。

接口定义方法也比较清楚,主要实现方法就是事务的挂起和恢复,和四个事务状态切换时的回调方法。
作为事务同步回调操作,具体是在TransactionSynchronizationManager中被管理和调用。TransactionSynchronizationManager在事务执行过程中承担一个非常重要的作用,负责管理线程级别的资源和事务同步回调接口,其通过一系列静态方法调用线程级别的资源管理方法。

TransactionSynchronizationManager通过ThreadLocal线程私有变量,来保证线程之间事务互不影响,除了下面四个ThreadLocal变量用来标识事务属性和状态外,最主要的就是resources和synchronizations变量了。该类的主要功能也是通过这两个变量来实现。
resources变量很好理解,作为事务挂起和恢复,和线程挂起是一样的,需要保护现场,将当前事务资源保存起来,在恢复事务时,则通过该资源进行恢复。我们找一个例子。
在事务获取的过程中,会对已存在的事务进行处理,其中会首先判断事务传播行为,当碰到REQUIRES_NEW时,将会挂起当前事务,并新起事务进行执行。我们看看事务挂起是如何通过线程级别的变量来进行管理的。

我们直接进入到DatasourceTransactionManager里,看看子类的抽象方法实现。

可以看到,通过bindResource()和unbindResource()方法,以当前数据源作为key,事务挂起资源为value,进行了事务中的资源管理(其中suspendedResources中包含了事务的基础属性和必备的事务资源),实际就是将资源放进了ThreadLocal的Map中,以便再次使用。
标准事务流程
大概介绍完一些基本概念之后,我们来看看Spring的标准事务流程。
在文章开头的图上可以看到,Spring事务通过配置事务切面来进行事务方法的拦截,其中注册了TransactionInteceptor。

这是TransactionInterceptor的继承类图,关于事务的操作,由MethodInteceptor拦截事务方法,其父类TransactionAspectSupport通过操作PlatformTransactionManager实现事务管理。

当拦截器拦截到事务方法后,委托给父类方法TransactionAspectSupport#invokeWithinTransaction()。

方法进来,首先通过获取到从事务方法中解析出来的事务属性源(事务属性源配置在TransactionManagementConfiguration中,可以认为是程序中所有事务方法上的事务属性解析后的集合),通过方法和类从事务属性源获取到被拦截方法的对应的配置属性,并通过该事务属性获取到TransactionManager(用户通过@Transactional注解指定或默认)。

开启事务
首先是开启一个事务,调用被拦截方法,异常捕获时根据用户配置进行提交或回滚(rollbackOn || notRollbackOn),正常执行则提交事务。这个逻辑没问题,就是以我们平时事务方法执行流程进行,Spring事务在这边帮我们做了第一层的封装。
我们看看createTransactionIfNecessary()方法。

其中主要的是事务状态的创建,通过TransactionManager#getTransaction(TransactionAttribute)获取一个新的事务状态(事务的获取与开启放止稍后与事务提交回滚一同细讲)。
至此,在TransactionAspectSupport中成功在执行被拦截方法前获取了事务对象,并开启了事务,在当前线程对应的数据库连接中,之后的数据库操作都属于同一个事务(autoCommit已经在获取事务时被设置成false)。
调用方法执行
获取事务对象后,对拦截方法进行调用。
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
// 执行方法调用
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
// target invocation exception
// 事务发生异常的操作
// 回滚或提交 | 取决于用户配置
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}
在方法调用发生异常时,执行TransactionAspectSupport#completeTransactionAfterThrowing(TransactionInfo, Throwable),并将之前的事务属性还原。其中completeTransactionAfterThrowing执行了用户配置的异常回滚条件。
// TransactionAspectSupport#completeTransactionAfterThrowing

同样,回滚和提交操作也是直接委托给持有的TransactionManager。
提交事务
// 返回提交事务并返回结果
commitTransactionAfterReturning(txInfo);
return retVal;
拦截方法成功执行后,提交事务并返回结果。
// TransactionAspectSupport#commitTransactionAfterReturning

这就是Spring定义的标准事务流,流程下来其实非常简单,和我们自己手写逻辑是差不多的,Spring主要的工作是定义了这套标准事务流程,具体使用什么底层存储来保证事务的成功执行,可以通过用户自定义TransactionManager来完成。
TransactionManger
接下来我们以DataSourceTransactionManager为例子,介绍一下TransactionManager对事务的控制是如何通过JDBC落实到DB的。以下均已DataSourceTransactionManager作为实现类进行分析。
获取事务并开启事务
// AbstractPlatformTransactionManager#getTransaction(TransactionDefinition)

对于获取到的事务对象,需要进行当前是否已存在事务的判断。判断方法为检查当前事务方法中的连接isTransactionActive的状态(该状态在事务被开启,即调用doBegin()方法时被置为true,表示该连接事务状态被激活)。
当前存在事务则进行和已存在事务相关的传播行为判断(关于事务挂起的大概流程即同上文所述保存状态,并初始化当前事务状态属性,例如修改isTransactionActive=false):
- NERVER:直接抛出异常。
- NOT_SUPPORTED:挂起已存在事务,并非事务地执行(给定事务对象为null)。
- REQUIED_NEW:挂起已存在事务,并创建新的事务进行执行。
- NESTED:嵌套事务执行。
关于NESTED行为,Spring给出两套解决方案,一个是基于JDBC3.0的Savepoint机制,通过在当前事务中记录保存点,来区分嵌套事务,当当前事务执行异常时,事务将回滚到记录的Savepoint来保证嵌套事务的部分回滚;另一个是通过简单的嵌套事务进行执行(只支持JTA),即重新开启一个事务,嵌套commit和rollback来进行嵌套事务管理。
当前如果不存在事务,则按照事务传播行为进行事务的创建或无事务执行或抛出异常(MANDATORY)。
doGetTransaction()
通过委托给子类实现的doGetTransaction()方法获取一个事务对象。对于DataSourceTransactionManager来说,获取事务对象的方法如下。

可以看到,基于JDBC相关的DataSourceTransactionManager的事务资源主要是数据库连接,通过获取到线程私有的数据库连接,来获取到事务对象。
doBegin()
// DataSourceTransactionManager#doBegin()

该方法主要工作就是做了事务开启的准备工作,例如设置只读标记、关闭自动提交、设置超时时间等。最主要的是将数据库连接绑定到当前的线程上。
回滚事务
// AbstractTransactionManager#rollback

// AbstractTransactionManager#processRollback

关于事务的回滚,可以说的就多了。
在上面获取事务的时候介绍过,Spring事务支持两种事务的嵌套方式,一种是JDBC 3.0的Savepoint机制,一种是JTA的嵌套开启事务。在processRollback方法中,针对不同的事务嵌套方式进行了不同的回滚策略。
首先判断事务是否支持JDBC 3.0的Savepoint机制,如果支持则直接ROLLBACK TO SAVEPOINT,并释放该Savepoint;如果当前事务是新事务,则直接回滚;否则则通过设置TransactionStatus的rollbackOnly标记来标记后续提交时可能需要回滚而非提交。
doRollback()
// DataSourceTransactionManager#doRollback()

可以看到,DataSourceTransactionManager的回滚操作就是通过当前事务持有的数据库连接进行回滚。
提交事务
// AbstractPlatformTransactionManager#commit

在事务提交之前,会先根据事务回滚标记类型来进行当前事务回滚或进行全局事务回滚(即整个大事务回滚)。否则正常进行回滚,调用processCommit()。
// AbstractPlatformTransactionManager#processCommit


doCommit()
同样,事务的提交也是简单地通过连接提交落实到DB。

从上面的逻辑可以看到,事务提交并没有很多复杂的逻辑,只是在事务真正提交前后,触发了很多回调接口方法,也就是我们前面提过的TransactionSynchronization。因此,我们其实可以自行注册TransactionSynchronization来进行事务提交前后的逻辑,例如可以在订单成功入库之后进行日志的记录,而不影响业务代码的可读性。
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
logger.info("订单准备提交");
}
@Override
public void afterCommit() {
logger.info("订单提交成功");
}
});
编程式事务
以上大概介绍了Spring是如何帮我们完成声明式事务的,但声明式事务有一个比较明显的缺点,就是事务的粒度太粗。在一些场景使用编程式事务可能能更细粒度地控制事务的进行,方便我们的开发。
通过上文的介绍,我们能想到,其实可以注入一个PlatformTransactionManager来进行事务管理,整个流程其实就是标准事务流程。下面是编程式事务的两种使用方式。
PlatformTransactionManager
// 声明事务定义
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 事务属性配置 def.set...()
def.setReadOnly(false);
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 获取事务对象(状态)
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务代码...
transactionManager.commit(status);
} catch (Exception e) {
// 业务代码执行失败
transactionManager.rollback(status);
}
TransactionTemplate
boolean result = transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
try {
// 业务代码...
} catch (Exception e) {
// 设置回滚标记 尝试提交时会进行回滚
status.setRollbackOnly();
return false;
}
return true;
}
});
该使用方法传入的TransactionCallback为FunctionalInterface,直接编写我们的业务代码即可。
区别
Note: The transaction manager should always be configured as bean in the application context: in the first case given to the service directly, in the second case given to the prepared template.
TransactionTemplate的注释中写道,事务管理器应该作为配置存在于应用上下文中,直接供其他服务类或预备模板使用。
总结
Spring事务究其根源就是最基础的事务逻辑,而框架为我们封装了标准事务流程,提供了更多的扩展性,使我们能更简单地使用事务,并且对业务代码几乎没有侵入性,开发人员更多的心思可以放在业务代码的编写上。
阅读Spring源码的收获除了了解该模块对应的实现逻辑外,最大的收获应该是从源码中获得一些编码规范、设计模式和方案上的成长,这对平时工作中业务代码的开发能力提升其实是非常显著的。在开发过程中,写出这种源码级别的代码,不仅提高项目质量,提高扩展性、可读性,更重要的是写出来的代码犹如艺术品,完成之后带来的成就感,可能就是我成为一名程序员所真正追求的东西。
日常挖坑
下一篇文章应该是SpringMVC的源码阅读笔记,源码和笔记已经完成,文章也即将在路上了~
欢迎关注我,共同学习成长~