Spring Boot 声明式事务

1. 生效原理

1.1 编写测试Demo来测试事务

编写一个普通的 Service 来简单构造一个事务场景。

@Service
public class DemoService {

    @Transactional(rollbackFor = Exception.class)
    public void test1() {
        System.out.println("test1 run...");
        int i = 1 / 0;
        System.out.println("test1 finish...");
    }

}

在启动类上标注 @EnableTransactionManagement 注解来启动注解事务。

@EnableTransactionManagement
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
        DemoService demoService = ctx.getBean(DemoService.class);
        demoService.test1();
    }

}

运行主启动类,发现控制台没有打印 test1 finish... ,并输出异常信息。

并且从控制台的异常信息栈中发现了cglib的身影,因为编写的 Service 没有接口,使用cglib创建的代理对象。

接下来咱来开始分析注解声明式事务的生效原理。

1.2 @EnableTransactionManagement

注解内部的定义咱暂且不关心,只记住默认使用 PROXY - 代理方式来增强代码即可。

@EnableTransactionManagement 注解上面声明了 @Import ,它导了一个Selector:TransactionManagementConfigurationSelector

1.3 TransactionManagementConfigurationSelector

咱已经很清楚, ImportSelector 的作用是筛选组件,返回组件的全限定类名,让IOC容器来创建这些组件。

@EnableTransactionManagement 注解默认使用 PROXY 来增强事务,那这个switch结构中就应该返回两个类的全限定类名:AutoProxyRegistrarProxyTransactionManagementConfiguration ,可以看得出来,声明式事务最终起作用是上述两个组件的功能。下面咱分别来看这两个类。

1.4 AutoProxyRegistrar

它又实现了 ImportBeanDefinitionRegistrar ,又是手动向IOC容器中导入组件。

注意中间部分的一个if判断:如果 @EnableTransactionManagement 注解中设置 adviceModePROXY (默认PROXY),则会利用 AopUtils 创建组件,并且如果 @EnableTransactionManagement 设置 proxyTargetClass 为true,则还会额外导入组件(默认为false)。下面咱看看它又向容器里注册了什么组件。

1.4.1 AopUtils.registerAutoProxyCreatorIfNecessary

从上面的方法一级一级向下执行,最终来到 registerOrEscalateApcAsRequired 方法(注意在第二层方法中传入了一个 InfrastructureAdvisorAutoProxyCreator.class (基础增强器自动代理创建器),可能下面就是注册这个类型的组件)。

(仔细观察一下,这种类名的命名风格分明就是AOP组件的命名诶。。。先保留这个疑问,继续往下看)

先看看它传的这个 InfrastructureAdvisorAutoProxyCreator 类是什么东西吧:

1.4.1.1 InfrastructureAdvisorAutoProxyCreator

文档注释原文翻译:

Auto-proxy creator that considers infrastructure Advisor beans only, ignoring any application-defined Advisors.

自动代理创建器,仅考虑基础结构Advisor类型的Bean,而忽略任何应用程序定义的Advisor。

注释解释的不是很清楚,咱再看看这个类的继承结构:

img

可以发现它也是个后置处理器,并且是在Bean创建前后执行的后置处理器(InstantiationAwareBeanPostProcessor),而且它来自 spring-aop 包。那既然是这样,它与之前AOP部分咱看到的思路就完全一致了(该类/父类中一定会有寻找增强器、过滤增强器,最终生成代理包装Bean为代理对象的方法)。

回到上面的 AopUtils 类,registerAutoProxyCreatorIfNecessary 方法注册了一个 InfrastructureAdvisorAutoProxyCreator ,跟之前咱在AOP部分看到的 @EnableAspectJAutoProxy 注解注册的 AnnotationAwareAspectJAutoProxyCreator 几乎完全一致了,那下面的方法也不用看了,思路真的完全一致。

1.5 ProxyTransactionManagementConfiguration

注意这个配置类还继承了父类,父类的配置也会被加载。

很明显它向IOC容器中注册了3个Bean。一个一个来看:

1.5.1 transactionAdvisor:事务增强器

BeanFactoryTransactionAttributeSourceAdvisor 的文档注释原文翻译:

Advisor driven by a TransactionAttributeSource, used to include a transaction advice bean for methods that are transactional.

TransactionAttributeSource 驱动的增强器,用于为开启事务的Bean的方法附加事务通知。

文档注释大概是描述是它给业务方法增强事务通知,咱先放一边。注意看这个类名的最后:Advisor ,它是一个增强器!

看一眼这个类的继承和一些成员:

从这部分源码中可以得知非常关键的点:它是利用切入点来增强方法 (源码中看到了pointcut)。源码中的pointcut属性的创建又要借助 TransactionAttributeSource 。这部分依赖关系如下:

1.5.1.1 TransactionAttributeSourcePointcut

通过前面AOP部分的阅读,咱也知道,所有的切入点类都会实现 Pointcut 接口,TransactionAttributeSourcePointcut 的类继承和部分源码:

它实现了 ClassFilter 接口(matches 是重写的方法,源码不再展开), matches 方法有两部分判断逻辑:是否为 TransactionalProxyPlatformTransactionManagerPersistenceExceptionTranslator 的实现类,以及让 TransactionAttributeSource 获取事务属性看是否为空。前半部分好理解,后半部分需要借助 TransactionAttributeSource 来判断,正好配置类中事务增强器的下边就要创建一个 AnnotationTransactionAttributeSource ,那咱就继续往下看。

1.5.2 AnnotationTransactionAttributeSource:注解事务配置源

创建出来的只是一个普通的 AnnotationTransactionAttributeSource 而已,它的文档注释原文翻译:

Implementation of the org.springframework.transaction.interceptor.TransactionAttributeSource interface for working with transaction metadata in JDK 1.5+ annotation format. This class reads Spring's JDK 1.5+ Transactional annotation and exposes corresponding transaction attributes to Spring's transaction infrastructure. Also supports JTA 1.2's javax.transaction.Transactional and EJB3's javax.ejb.TransactionAttribute annotation (if present). This class may also serve as base class for a custom TransactionAttributeSource, or get customized through TransactionAnnotationParser strategies.

org.springframework.transaction.interceptor.TransactionAttributeSource接口的实现,用于处理JDK 1.5+注释格式的事务元数据。

此类读取Spring的 @Transactional 注解,并将相应的事务属性公开给Spring的事务基础结构。此外,还支持JTA 1.2的 javax.transaction.Transactional 和EJB3的 javax.ejb.TransactionAttribute 注解(如果存在)。此类也可用作自定义 TransactionAttributeSource 的基类,或通过 TransactionAnnotationParser 策略进行自定义。

说了这么多,我们只关心一句话:它读取 @Transactional 注解。由此可见 AnnotationTransactionAttributeSource 是读取 @Transactional 注解的。

上面的Bean在创建时直接调了构造方法,这个构造方法咱还是要看一下的:

注意下面重载的构造方法中,它给 annotationParsers 中添加了一个 SpringTransactionAnnotationParser

1.5.2.1 SpringTransactionAnnotationParser

这里面的核心方法如上述源码,可以发现它的核心功能是解析 **@Transactional** 注解的信息

至此上面的配置类中需要的事务增强器、事务切入点、事务配置源、事务注解解析器都解析完,回到配置类中,还有一个拦截器:

1.5.3 TransactionInterceptor

在Bean的创建过程中,它也把事务配置源保存起来了,并且还注入了事务管理器。而 TransactionInterceptor 本身的类定义:

发现它实现了 MethodInterceptor !它也是一个AOP的增强器。那它的核心作用大概率就是控制事务咯?咱先不着急,它的工作原理咱到下一篇再看,本篇先把需要配置的组件都解析完。


ProxyTransactionManagementConfiguration 的配置读完之后,别忘了它还继承了一个父类,下面咱看看这个父类里都干了什么:

1.6 AbstractTransactionManagementConfiguration的配置

可以发现在最底下它又创建了一个组件,类型是 TransactionalEventListenerFactory

1.6.1 TransactionalEventListenerFactory

它的文档注释原文翻译:

EventListenerFactory implementation that handles TransactionalEventListener annotated methods.

EventListenerFactory的实现类,用于处理带有 @TransactionalEventListener 注解的方法。

发现它又提到了一个注解:@TransactionalEventListener ,实际上 TransactionalEventListenerFactory 这个组件是做事务监听机制的。

【如果小伙伴还不是很了解 @TransactionalEventListener ,请继续往下看,熟悉的小伙伴请跳过6.1节】

1.6.1.1 【扩展】@TransactionalEventListener

自 SpringFramework4.2 之后,出现了一种能在事务动作发生前后注入监听器的机制。

举几个应用场景的例子:

  • 执行完数据库操作后发送消息

  • 执行数据库操作之前记录日志

  • 业务逻辑出错时事务回滚之后发邮件警报

类似于这种事务动作执行前后进行附加操作的问题,在SpringFramework4.2之后就可以通过 @TransactionalEventListener 注解来实现。

@TransactionalEventListener 可提供4种监听时机,来执行附加操作:

  • BEFORE_COMMIT:提交之前

  • AFTER_COMMIT:提交之后

  • AFTER_ROLLBACK:回滚之后

  • AFTER_COMPLETION:事务完成之后

1.6.1.2 @TransactionalEventListener的使用方式简单Demo

1.7 小结

  1. Spring的注解事务底层是借助AOP的机制,创建了一个 InfrastructureAdvisorAutoProxyCreator 组件来创建代理对象。

  2. 注解事务要想生效,需要事务增强器、事务切入点解析器、事务配置源、事务拦截器等组件。

2. 工作原理

2.0 测试Demo

@Transactional 标注的注解所在类,在IOC容器初始化时被动态代理为一个代理对象。由于上面定义的Service是类,其代理方式为cglib代理。故加事务的方法在执行时首先被调用的方法是:intercept

下面将以Debug步骤来逐步观察和分析事务控制流程。

2.1 intercept

整个方法在之前的AOP部分分析过了,关键的步骤是上述源码中的两步:获取拦截器调用链,执行代理+目标方法的调用。

第一步获取拦截器链的内容过程咱就不看了,前面AOP部分已经分析过了,咱看下返回的拦截器都有什么:

img

只有一个拦截器,而且恰好是上一篇分析的 TransactionInterceptor

下面进入proceed方法。

2.2 proceed

第一次进入 proceed 方法,由于此时 -1 ≠ (1 - 1) ,第一个if结构不进入。

img

自然进入下面的拦截器执行部分, TransactionInterceptor 不属于 InterceptorAndDynamicMethodMatcher ,自然走下面的else结构,执行 invoke 方法。

2.3 invoke

来到 TransactionInterceptor

首先做一次目标方法执行的空校验,AopUtils.getTargetClass() 是为了获取被代理的目标类,之后执行 invokeWithinTransaction 方法,套用事务。

2.4 invokeWithinTransaction

该方法在父类 TransactionAspectSupport 中定义。

由源码可以很明显看出来,它使用的是环绕通知

下面的 try-catch-finally 中,可以发现方法正常执行后,没有问题,会在finally块下面执行 commitTransactionAfterReturning 方法来提交事务,出现异常时会进入catch块,执行 completeTransactionAfterThrowing 方法来回滚事务。

下面根据不同情况来分别Debug看效果。

2.4.1 成功提交事务

将DemoService的test方法中去掉除零运算,Debug运行之后一切正常,try块没有抛出异常,进入下面的 commitTransactionAfterReturning 方法:

准备进入 commitTransactionAfterReturning 方法时的Debug状态:

img

下面进入 commitTransactionAfterReturning 方法:

2.4.1.1 commitTransactionAfterReturning

核心方法很简单:拿到事务管理器,执行 commit

2.4.1.2 commit

2.4.1.3 processCommit

由于当前测试的Demo仅仅是单方法事务,所以它是一个全新的事务,进入else-if块,执行 **doCommit** 方法。(又看到xxx和doXXX了)

2.4.1.4 doCommit

至此发现了我们熟悉的面孔,这也是jdbc最底层的API:拿 Connection 对象,调用 **commit** 方法,事务成功提交。

2.4.2 失败回滚事务

将DemoService的test方法中加入除零运算,Debug运行之后发现出现异常,进入 invokeWithinTransaction 方法中的 catch 块:

准备进入 completeTransactionAfterThrowing 方法时的Debug状态:

img

下面进入 completeTransactionAfterThrowing 方法:

2.4.2.1 completeTransactionAfterThrowing

在回滚之前,它要判定抛出的异常类型。

2.4.2.1.1 rollbackOn

可以发现它是校验异常的类型是否为 RuntimeExceptionError

判断完成后,回到 completeTransactionAfterThrowing 方法,它要拿事务管理器来调 rollback 方法。

2.4.2.2 rollback

方法中先校验一下事务是否已经完成,之后会执行 processRollback 方法。

2.4.2.3 processRollback

测试Demo依然是单事务,进入 doRollback

2.4.2.4 doRollback

又发现了我们熟悉的jdbc操作:拿 Connection 对象,调用 **rollback** 方法,事务成功回滚。

2.5 小结

  1. 声明式事务由动态代理进入,核心方法是 invokeWithinTransaction

  2. 事务管理器的提交和回滚最终是调用jdbc的底层API来进行提交和回滚。

3. 事务传播行为原理

前面的两篇咱们看了声明式事务的生效原理和工作原理,咱们也知道Spring有7种事务传播行为,这个在开发中也是可能会遇到的。本篇和下一篇会解析声明式事务的事务传播行为原理。

事务传播行为的7种类型:

事务传播行为
描述

PROPAGATION_REQUIRED

【默认值:必需】当前方法必须在事务中运行,如果当前线程中没有事务,则开启一个新的事务;如果当前线程中已经存在事务,则方法将会在该事务中运行。

PROPAGATION_SUPPORTS

【支持】当前方法单独运行时不需要事务,但如果当前线程中存在事务时,方法会在事务中运行

PROPAGATION_MANDATORY

【强制】当前方法必须在事务中运行,如果当前线程中不存在事务,则抛出异常

PROPAGATION_REQUIRES_NEW

【新事务】当前方法必须在独立的事务中运行,如果当前线程中已经存在事务,则将该事务挂起,重新开启一个事务,直到方法运行结束再释放之前的事务

PROPAGATION_NOT_SUPPORTED

【不支持】当前方法不会在事务中运行,如果当前线程中存在事务,则将事务挂起,直到方法运行结束

PROPAGATION_NEVER

【不允许】当前方法不允许在事务中运行,如果当前线程中存在事务,则抛出异常

PROPAGATION_NESTED

【嵌套】当前方法必须在事务中运行,如果当前线程中存在事务,则将该事务标注保存点,形成嵌套事务。嵌套事务中的子事务出现异常不会影响到父事务保存点之前的操作。

3.0 修改测试Demo

修改测试代码如下:

默认情况下,SpringFramework 中 @Transactional 的事务传播行为是 Propagation.REQUIRED

Support a current transaction, create a new one if none exists.

支持当前事务,如果不存在则创建新事务。

在上面的测试代码中,应不会打印 "test2 finish..."

这其中的工作机制要回到 invokeWithinTransaction 方法中的 createTransactionIfNecessary 方法中,这部分会真正的开启事务。

下面咱还是以Debug的方式来分步调试,观察事务的开启时机:

3.1 【REQUIRED】第一次Debug

3.1.1 invokeWithinTransaction

咱们只关注关键部分:

createTransactionIfNecessary 方法会根据切入点判断是否需要开启事务,而切入点就是要执行的 test2 方法。

img

3.1.2 createTransactionIfNecessary

上面先指定了当前事务的名称,下面会获取事务状态,而这个事务状态要从 DataSourceTransactionManager 中获取。

3.1.3 getTransaction

第一个 definition 的判断是否为空,Debug发现它不为null,经过方法调用栈的追溯,发现它来自 invokeWithinTransaction 方法:

好吧,我们之前没有抓到这个点,那我们来重新Debug。

3.2 【REQUIRED】第二次Debug

把断点打在 tas.getTransactionAttribute 上,重新Debug,并进入到这个方法,发现来到了 AbstractFallbackTransactionAttributeSource 中。

在这个类中,attributeCache 是一个 Map 。通过Debug,走到 this.attributeCache.get(cacheKey) 这一句时发现返回值不为null,直接返回走了!在咱看来这个方法是第一次执行,而且是我在主启动类里手动调用的,为什么 attributeCache 里会有缓存呢?

注意观察上面方法中的else部分,有对 attributeCacheput 操作,由此大概可以断定是之前有执行过这个方法,当时 attributeCache 中还没有,才进入到else中,对 attributeCache 执行 put 操作。

我们把断点打在else中的第一行,再次Debug。

3.3 【REQUIRED】第三次Debug

getTransactionAttribute 的else结构中追踪,发现在IOC容器启动时就已经执行进来了。而执行该方法的调用栈中发现了一个方法:

img

而且往上看还看到了 wrapIfNecessary ,证明这是在AOP部分就已经触发了事务信息的加载

仔细看这部分的方法调用,咱会发现这部分其实它想找一些可以应用在当前创建Bean的增强器。

往上倒一级,看 TransactionAttributeSourcePointcutmatches 方法:

发现它在这里来触发加载事务定义信息的。(其实在第21篇的5.1.1节已经介绍过它了,不再赘述)

那咱大概就知道了它的触发时机了:因为在之前开启注解事务时,触发自动配置,而自动配置中注入了一个 **InfrastructureAdvisorAutoProxyCreator** ,它配合 **BeanFactoryTransactionAttributeSourceAdvisor** (事务增强器)来完成事务织入,在第一次事务织入时要获取所有切入点,之后它会搜索所有切入点,判断创建的Bean是否可以被织入事务通知 ,在搜索时刚好来到这里要解析事务定义信息,所以会触发解析和缓存动作。

3.3.1 继续往下走,回到getTransaction

确定事务定义信息不为空后,下一步要调用 isExistingTransaction ,判断当前线程中是否存在事务。

3.3.2 isExistingTransaction

来到 DataSourceTransactionManager

从return的结构中看出,如果 ConnectionHolder 存在且激活,就表明当前线程已经存在事务。

通过Debug,发现 ConnectionHolder 为null,这个方法返回false,不进入上面的片段。

3.3.2.1 ConnectionHolder

文档注释原文翻译:

Resource holder wrapping a JDBC Connection. DataSourceTransactionManager binds instances of this class to the thread, for a specific javax.sql.DataSource. Inherits rollback-only support for nested JDBC transactions and reference count functionality from the base class. Note: This is an SPI class, not intended to be used by applications.

包装JDBC连接的资源持有者。对于特定的 javax.sql.DataSourceDataSourceTransactionManager 将此类的实例绑定到线程。

从父类继承对嵌套JDBC事务和引用计数功能的仅回滚支持。

注意:这是SPI类,不适合应用程序使用。

文档注释很容易理解,它是持有jdbc的 Connection 对象的,DataSourceDataSourceTransactionManager 可以借助它实现线程绑定。

3.3.3 判断超时和事务传播行为类型

这部分先判断超时时间的设置是否合理(默认的 TransactionDefinition.TIMEOUT_DEFAULT = -1),之后下面要筛选事务传播行为类型:

  • 如果是MANDATORY类型,则直接抛出异常,因为此时还没有事务

  • 如果是REQUIREDREQUIRES_NEWNESTED类型,则创建一个新的事务

  • 其余情况,返回空事务

下面咱先以REQUIRED行为来继续Debug,看它的处理方式。

3.3.4 【REQUIRED】进入else if结构

这里面执行了几个关键的步骤:

  • 挂起null(相当于无操作)

  • 创建一个新的事务状态,并标记为新事务【关键】

  • 开启事务连接【关键】

  • 准备事务同步工作

分步骤来看:

3.3.4.1 suspend(null)

这个方法既然是挂起和恢复的,从这段实现中只有一个点是我们应该关注的:doSuspend

3.3.4.1.1 doSuspend

这个方法的逻辑比较简单,它会获取上一次事务的数据源连接对象,并将其从当前 ThreadLocal 中移除。这里面获取数据源的部分很简单:

关键的部分在 TransactionSynchronizationManager.unbindResource 中:

3.3.4.1.2 TransactionSynchronizationManager.unbindResource

又看到了doXXX,进到 doUnbindResource 中:

3.3.4.1.3 doUnbindResource

可以看到它对 ThreadLocal 中的事务对象进行移除操作,完成事务解除绑定。

至此,suspend(null) 执行完毕。

3.3.4.2 newTransactionStatus

注意源码中传入构造方法中的参数 newTransaction :true (第三个参数),标明马上要开启一个新事务

3.3.4.3 doBegin

try块中的第一个if结构体中,看到了 obtainDataSource().getConnection() ,获取到真正的数据库连接。

之后下面的if结构中,发现了 con.setAutoCommit(false) ,表明关闭自动提交,即开启事务

3.3.4.4 prepareSynchronization

这部分是将事务状态和事务定义信息放入事务同步管理器中,逻辑很简单,不再展开。

3.3.5 回到createTransactionIfNecessary

最后一步准备事务信息:

3.3.6 prepareTransactionInfo

中间大段的日志打印就不看了,最后有一个 txInfo.bindToThread()

3.3.7 txInfo.bindToThread()

可以发现又是直接把当前的事务信息放入 ThreadLocal 中。

至此,createTransactionIfNecessary 方法执行完成,DemoService2 的事务成功创建。

3.4 【REQUIRED】DemoService2执行DemoService

DemoService2test2 执行中,会执行 DemoServicetest1 方法。

此时又会触发开启事务,来到 invokeWithinTransaction 方法:

3.4.1 invokeWithinTransaction

再次进入 createTransactionIfNecessary 方法:

3.4.2 createTransactionIfNecessary

进入 getTransaction

3.4.3 getTransaction

首先去获取事务:

之后去下面的 isExistingTransaction 方法,很明显此时已经存在事务,进入 handleExistingTransaction 方法。

3.4.4 handleExistingTransaction

关键的判断逻辑都在注释中标注好了,默认情况下 @Transactional 注解中的事务传播行为是REQUIRED,均不属于上面的if判断结构条件,最终到最后的 prepareTransactionStatus 方法,返回出去,全程没有再开启新事务,也没有挂起事务。

至此,REQUIRED模式得以体现。

3.5 【REQUIRES_NEW】过程

DemoServicetest1 方法中 @Transactional 注解的 propagation 修改为 REQUIRES_NEW 。重新Debug,来到 handleExistingTransaction 方法中:

进入这一组分支中,这里面的步骤与之前几乎完全一致,这里咱只关注几个不太相同的部分。

3.5.1 suspend执行完

通过Debug发现它返回了test2的事务信息:

img

3.5.2 创建TransactionStatus后

通过Debug发现它的 suspendedResources 包含了test2的事务:

img

3.5.3 test1方法commit

在清理test1的事务缓存后,最底下有一个 resume 方法,它负责激活上一个事务:

3.5.4 resume

它做空校验后,会拿到当前线程中的上一个事务,并执行 doResume 方法,最终再绑定到事务同步管理器上。

3.5.5 doResume

发现这里重新绑定了之前被挂起的事务。

至此,REQUIRES_NEW模式也得以体现。

3.6 【NESTED】Debug过程

DemoServicetest1 方法中 @Transactional 注解的 propagation 修改为 NESTED 。重新Debug,来到 handleExistingTransaction 方法中:

这部分流程就跟之前不太一样了,因为涉及到保存点的概念。下面还是Debug到方法的if结构中来看:

3.6.1 useSavepointForNestedTransaction

没什么好说的,直接进if结构体吧。

3.6.2 prepareTransactionStatus

这里面分为两个部分:创建 TransactionStatus ,设置同步。这两步也很简单,之前也都看过了,不再赘述。

3.6.3 status.createAndHoldSavepoint

这里是设置保存点的部分。

很明显核心的部分是拿到 SavepointManager 调用 createSavepoint 方法。

3.6.3.1 getSavepointManager

很简单,它只是把当前的事务做了一次强转。

3.6.3.2 createSavepoint

这里面它拿 ConnectionHolder ,最底下的return中调了 createSavepoint 方法来实际的创建保存点。

发现了原生jdbc的操作:Connection 对象的 setSavepoint 方法。

3.6.4 test1方法commit

与之前没什么不同,直接commit即可。最后的清除缓存部分,因为当前事务不是全新的事务,所以没有任何动作,直接返回。

3.7 小结

  1. 声明式事务有7种事务传播行为,默认是REQUIRED。

  2. 事务传播行为的加载过程,是在事务通知织入代理对象时已经创建好了。

  3. 事务传播行为的核心控制点在 getTransactionhandleExistingTransaction 方法中。

最后更新于

这有帮助吗?