Spring 源码学习笔记11——Spring事务
Spring事务是基于Spring Aop的扩展
AOP的知识参见《Spring 源码学习笔记10——Spring AOP》
图片参考了https://www.processon.com/view/60f4d859e0b34d0e1b6bb40c?fromnew=1
逻辑事务和物理事务的概念来自https://wiyi.org/physical-and-logical-transactions.html
本文忽略了编程式事务,探究了基于事务注解的申明式事务
一丶spring事务相关概念
事务是指访问并更新数据库中各种数据项的一个程序执行单元,在事务中的操作,要么都做修改,要么都不做。
1.事务传播级别
事务传播级别决定了事务的控制范围,这似乎不是数据库层面的概念。
1.1.required
Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行
1.2.supports
如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行
1.3.mandatory
该传播级别要求上下文中必须存在事务,否则抛出异常
1.4.requires_new
该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)但是内部调用的方法抛出异常,也会导致外部回滚
1.5.not_supported
当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)
1.6.never
该传播级别要求上下文中不能存在事务,否则抛出异常。
1.7.nested
嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务
2. 物理事务和逻辑事务
2.1物理事务
所谓物理事务指的就是Connection开启的事务。
2.2逻辑事务
在一个复杂的业务系统中,可能会调用多个service,每个service都有自己的事务(标注了@Transactional),此时我们需要根据事务传播方式(Propagation)来决定当前事务的行为(比如要挂起创建新事物,还是加入当前事务)。
我们可以认为每个注解了@Transactional的方法都是一个逻辑事务,这些逻辑事务被Spring事务管理,Spring会根据事务传播方式来决定是否开启新事务。
二丶@EnableTransactionManagement注解做了什么
在SpringBoot使用事务的时候我们通常会在配置类上面加一个@EnableTransactionManagement
注解来开启事务,其实不加也可以,SpringBoot有事务的AutoConfiguration。下来我们看下这个注解干了什么。
重点在于@Import(TransactionManagementConfigurationSelector.class)
,TransactionManagementConfigurationSelector
实现了ImportSelector
.最终会执行selectImports
为容器注入bean。
这里会注入AutoProxyRegistrar
和ProxyTransactionManagementConfiguration
.接下来我们看些这个两个类有什么作用
1.AutoProxyRegistrar
AutoProxyRegistrar
实现了ImportBeanDefinitionRegistrar
,会向容器中注入需要的BeanDefinition
,这里最终调用了AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)
,如果容器中没有名称为org.springframework.aop.config.internalAutoProxyCreator
的BeanDefinition那么会注入InfrastructureAdvisorAutoProxyCreator
InfrastructureAdvisorAutoProxyCreator
是AbstractAutoProxyCreator
的子类,实现了SmartInstantiationAwareBeanPostProcessor
会在getEarlyBeanReference
提前暴露代理对象或者在bean完成实例化初始化后调用postProcessAfterInitialization
对原始的bean进行aop增强,这部分我们在Spring 源码学习笔记10——Spring AOP 已经学习过了。也就是说这里会注入一个基于动态代理实现Aop增强的一个bean后置处理器
2.ProxyTransactionManagementConfiguration
-
注入
TransactionalEventListenerFactory
这一步是在其父类
AbstractTransactionManagementConfiguration
中完成的TransactionalEventListenerFactory
在Spring源码学习笔记5——注册BeanPostProcessor,初始化事件多播器,注册事件监听器中我们提到过,它会把标注TransactionalEventListener
的方法包装成一个ApplicationListener
来响应对应的事件。 -
注入
TransactionInterceptor
TransactionInterceptor
是一个Advise
一个基于方法拦截器的通知,在其中实现了事务的增强逻辑。Spring 源码学习笔记10——Spring AOP 中我们分析过最后的aop代理对象执行方法的时候会调用到方法拦截器的invoke
方法,TransactionInterceptor
便是在其中实现了事务的增强。 -
注入
BeanFactoryTransactionAttributeSourceAdvisor
BeanFactoryTransactionAttributeSourceAdvisor
是一个PointcutAdvisor
,是一个Advisor
,它使用Pointcut
来判断当前对象的方法是否需要增强,使用Advice
对方法进行增强。 -
注入
TransactionAttributeSource
这里注入的是
AnnotationTransactionAttributeSource
,主要负责从类上,方法上,获取@Transactional
注解信息,比如抛出什么异常回滚,事务超时时间等。
看完这个类,我们基本上清楚了Spring 事务是如何实现的了,首先是通过BeanPostProcessor与Spring IOC容器结合在一起,在bean实例化初始化后调用后置处理器(如果出现循环依赖那么调用的是提前暴露对象的getEarlyBeanReference
)对bean进行Aop增强,在AbstractAutoProxyCreator
的wrapIfNecessary
方法中,会获取全部的Advisor
,便会拿到注入的BeanFactoryTransactionAttributeSourceAdvisor
,然后使用Pointcut
进行过滤,然后通过ProxyFactory
选择使用JDK动态代理,还是cglib动态代理。后续调用代理对象的方法会调用到,TransactionInterceptor
中的invoke
实现了事务增强的逻辑。接下来我们详细看看细节部分
三丶Spring事务源码分析
1.BeanFactoryTransactionAttributeSourceAdvisor
实现了PointcutAdvisor
接口,我们看下它的Pointcut
到底是什么,它的Advice
又是什么。
1.1 Pointcut
BeanFactoryTransactionAttributeSourceAdvisor维护了一个TransactionAttributeSourcePointcut
类型的pointcut
,
它实现了StaticMethodMatcherPointcut
,它是一个静态方法匹配的Pointcut,这里的静态意思是不会因为入参的参数不同而改变过滤结果。我们看下具体的实现逻辑。
这里的getTransactionAttributeSource
返回的是AnnotationTransactionAttributeSource
实例,这里的逻辑是只要有TransactionAttributeSource
并且可以拿到事务定义信息,那么就视为匹配,后面就会对方法进行增强。
1.2 AnnotationTransactionAttributeSource是如何获取事务定义信息的
getTransactionAttribute
由其父类AbstractFallbackTransactionAttributeSource
实现,其使用一个ConcurrentHashMap
缓存方法和其对应的事务信息,如果缓存中没有那么会调用computeTransactionAttribute
方法进行获取。computeTransactionAttribute
方法会优先获取方法上面的事务注解信息,然后获取类类上面的注解信息。
可以看到事务注解只能标注在public方法上面,如果是像mybatis这种生成接口动态代理类那么会拿到接口上面的注解信息。
获取注解信息调用了子类的AnnotationTransactionAttributeSource#determineTransactionAttribute
方法
这里涉及到一个新的类TransactionAnnotationParser
,这是Spring根据AnnotatedElement
获取注解的接口,可以扩展此接口实现自己的事务注解,并定制事务定义信息。这些事务注解解析器在构造方法中进行了定义。
可以看到Spring内置了JTA
,ejb
的事务注解处理器,但是使用的是LinkedHashSet,SpringTransactionAnnotationParser
会放在最前面,解析Spring的·@Transactional·注解,这也是我们最常用的事务注解,SpringTransactionAnnotationParser
会拿到事务注解信息,然后把注解的属性内容包装成 RuleBasedTransactionAttribute
RuleBasedTransactionAttribute
对rollbackOn
进行了扩展,配合SpringTransactionAnnotationParser
会解析事务注解中的rollbackFor
,rollbackForClassName
,noRollbackFor
,noRollbackForClassName
属性来判断异常抛出时是否需要回滚事务。如果这个属性没有值的话,会调用父类的rollbackOn
,最终只会在RuntimeException
和 Error
上面回滚。
1.3 BeanFactoryTransactionAttributeSourceAdvisor是如何获取到Advice
的
它实现了BeanFactoryAware
,使用advice
持有当前通知的引用,如果没有那么从容器中根据名称拿。但是在ProxyTransactionManagementConfiguration
中直接设置advice
为容器中的TransactionInterceptor
,也就是说事务的增强逻辑定义在TransactionInterceptor
中。
2.TransactionInterceptor
TransactionInterceptor
是事务拦截器,其invoke方法会在代理对象被代理方法执行的时候被回调到
其中invokeWithinTransaction
是spring事务原理的关键,此方法在其父类TransactionAspectSupport
实现
2.1 利用TransactionAttributeSource获取方法或者类上的事务注解信息
事务注解决定了事务增强的代码执行逻辑,这里使用的TransactionAttributeSource
通常是是上文中我们提到的AnnotationTransactionAttributeSource
,上文中我们直到,其内部存在map缓存,如果缓存没有那么调用TransactionAnnotationParser
链式的进行解析,自然就调用到了SpringTransactionAnnotationParser
,SpringTransactionAnnotationParser
反射获取注解信息包装成TransactionAttribute
对象
2.2 获取事务管理器PlatformTransactionManager
PlatformTransactionManager
是spring抽象出的事务管理器接口,主要包含下面三个方法
public interface PlatformTransactionManager {
//根据指定的传播行为返回当前活动的事务或创建新事务。
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
//根据事务状态提交事务
void commit(TransactionStatus status) throws TransactionException;
//根据事务状态回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
具体实现我们后续遇到对应方法再说。
determineTransactionManager
方法决定使用什么事务管理器
这里可以看到Spring是支持@Transactional
注解value指定特定的事务管理器,但是我们实际使用中通常是没有指定的,这里就会去bean工程中获取PlatformTransactionManager
类型的bean,存在多个就会抛出异常了。这里拿到的一般是DataSourceTransactionManager
2.3 根据事务传播级别来决定是否由必要创建事务createTransactionIfNecessary
首先调用getTransaction
方法获取一个事务,然后调用prepareTransactionInfo
封装事务的处理配置信息并绑定到当前线程
其中getTransaction
在AbstractPlatformTransactionManager
中是一个final
的模板方法,它首先判断是否存在一个事务,如果存在那么调用handleExistingTransaction
来创建事务,如果不存在那么根据事务传播级别来决定是否创建事务。
-
不存在事务的情况
首先要求超时时长不能小于-1.-1表示的
使用底层事务系统的默认超时,如果不支持超时,则使用无
然后如果的传播级别是
MANDATORY
支持当前事务,如果不存在则抛出异常,也就是说MANDATORY
要求外层调用方法是在一个具备事务的情况下进行的调用.如果隔离级别是
Required(支持当前事务,如果当前不存在事务那么创建一个新事务)
,RequireNew(创建一个新事务,如果存在事务则暂停当前事务)
,Nested(如果当前事务存在,则在嵌套事务中执行)
那么会执行下面的逻辑首先是挂起当前事务,由于当前不存在事务,其实是把当前存在
TransactionSynchronization
事务同步回调的接口信息保存在SuspendedResourcesHolder
中。在DataSourceTransactionManager
中doBegin
方法会获取对应的Connection,然后根据事务定义对Connection进行设置,比如如果是只读事务那么会执行SET TRANSACTION READ ONLY
设置事务只读,设置超时时长,关闭自动提交等,并且把创建的事务信息绑定到resources ThreadLocal
中。prepareSynchronization
负责把事务相关信息设置到ThreadLocal中,并且初始化TransactionSynchronization
的LinkedHashSet
,这样我们我们通过TransactionSynchronizationManager
加入一些回调方法的时候不会抛NPE,之所以使用LinkedHashSet
是TransactionSynchronizationManager
支持Ordered接口
,@Order注解
进行排序如果是
SUPPORTS(支持当前事务,如果不存在,则以非事务方式执行)
,NOT_SUPPORTED(不支持当前事务;始终以非事务方式执行)
,NEVER(不支持当前事务,如果当前事务存在,则抛出异常)
那么不会开启事务,但是支持事务同步
-
存在事务的情况
-
是如何判断是否存在事务的
首先
doGetTransaction
方法会获取resources ThreadLocal
中的事务信息,这个事务信息是在doBegin
方法中绑定的然后调用
isExistingTransaction
来判断是否存在事务,同样
doBegin
方法会设置transactionActive
为true。
最后如果存在事务那么会执行下面的handleExistingTransaction
来根据事务传播级别来创建事务 -
handleExistingTransaction
首先如果传播级别是
NEVER(不支持当前事务,如果当前事务存在,则抛出异常)
当前存在事务,那么抛出异常如果传播级别是
NOT_SUPPORTED(不支持当前事务;始终以非事务方式执行)
那么不会开启事务,但是支持事务同步,并且会挂起当前事务,其中
prepareTransactionStatus
会初始化事务同步set,并且把当前事务的信息包装到DefaultTransactionStatus
并返回如果传播级别是
RequireNew(创建一个新事务,如果存在事务则暂停当前事务)
会挂起当前事务,从ThreadLocal中解绑,然后开启新事务,并初始化TransactionSynchronization`的set如果传播级别是
Nested(如果当前事务存在,则在嵌套事务中执行)
,首先会判断是否允许嵌套事务,如果不允许那么抛出异常,通常DataSourceTransactionManager
使用savepoint
来实现嵌套事务调用
Connection#setSavepoint
方法最后如果是
Required(支持当前事务,如果当前不存在事务那么创建一个新事务) SUPPORTS(支持当前事务;如果不存在,则以非事务方式执行) MANDATORY(支持当前事务;如果当前不存在事务,则抛出异常)
都不会产生新的事务。
-
2.4 prepareTransactionInfo包装事务信息并绑定到ThreadLocal
这里会使用oldTransactionInfo
记录之前的事务信息,并且绑定当前事务信息到ThreadLocal上,这样A事务方法调用B事务方法的时,能像栈一样先进后出。在invokeWithinTransaction
调用完业务方法后,会调用cleanupTransactionInfo
把oldTransactionInfo
重写设置到ThreadLocal中,这意味着B方法执行结束,回到了A方法的调用栈中。
2.5 回调业务逻辑InvocationCallback#proceedWithInvocation
其实调用的是MethodInvocation#proceed
,如果当前对象存在多层代理,比如先事务代理,再基于@AspectJ
注解的方法调用时长统计,那么后面代理增强也会执行,具体逻辑在ReflectiveMethodInvocation#proceed
方法中,如果还存在其他的拦截器链那么会继续执行拦截器中的逻辑,否则直接执行我们自己的业务逻辑代码,这部分在Spring 源码学习笔记10——Spring AOP 说到过。
2.6 completeTransactionAfterThrowing 业务逻辑出现异常时的处理
这里rollbackOn
取决于事务注解上面标注的在什么异常上回滚,在什么异常上不回滚,默认是在RuntimeException
和Error
上面才会回滚。
下面我们看下是如何回滚事务的
执行回滚DataSourceTransactionManager
是调用的Connection#rollback
方法,这里首先会获取ThreadLocal中的TransactionSynchronization
并且按照@Order
和Ordered
排序然后依次调用beforeCompletion
,如果具备回滚点,那么直接回滚到保存点,如果是一个新事务,那么直接回滚,如果不是一个独立的事务,只是标记需要回滚,执行完这些后,还会回调triggerAfterCompletion
,排序然后调用TransactionSynchronization#afterCompletion
方法,然后调用cleanupAfterCompletion
清理资源,并且恢复被挂起的线程。
2.7 commitTransactionAfterReturning提交事务
具体逻辑在processCommit
中进行
DataSourceTransactionManager
提交事务是执行的是Connection#commit
同样完成只会还会调用triggerAfterCommit
,triggerAfterCompletion
并清理ThreadLocal中的内容,并且恢复被挂起的事务。
3.事务同步回调接口TransactionSynchronization
其中beforeCommit
,beforeCompletion
,aterCommit
,afterCompletion
,只有当前事务时一个独立事务的时候才会回调,而且如果提交或者回滚的时候出现异常,beforeCompletion
,afterCompletion
也会被调用,我们可以通过TransactionSynchronizationManager#registerSynchronization
注册回调的逻辑。
那么什么时候事务被视作时一个独立事务昵
- 如果外部不存在一个事务,并且传播级别是
REQUIRED,REQUIRES_NEW,NESTED
- 如果外部存在一个事务,且传播级别为
REQUIRES_NEW
- 如果外部存在一个事务,且传播级别为嵌套事务,但是此时不是通过保存点来实现嵌套事务