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
来响应对应的事件。
- 注入
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
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;
}
具体实现我们后续遇到对应方法再说。
这里可以看到Spring是支持 @Transactional
注解value指定特定的事务管理器,但是我们实际使用中通常是没有指定的,这里就会去bean工程中获取 PlatformTransactionManager
类型的bean,存在多个就会抛出异常了。这里拿到的一般是 DataSourceTransactionManager
2.3 根据事务传播级别来决定是否由必要创建事务createTransactionIfNecessary
首先调用 getTransaction
方法获取一个事务,然后调用 prepareTransactionInfo
封装事务的处理配置信息并绑定到当前线程
其中 getTransaction
在 AbstractPlatformTransactionManager
中是一个 final
的模板方法,它首先判断是否存在一个事务,如果存在那么调用 handleExistingTransaction
来创建事务,如果不存在那么根据事务传播级别来决定是否创建事务。
* 存在事务的情况
最后如果存在事务那么会执行下面的
handleExistingTransaction
来根据事务传播级别来创建事务2.
handleExistingTransaction
首先如果传播级别是 NEVER(不支持当前事务,如果当前事务存在,则抛出异常)
当前存在事务,那么抛出异常 如果传播级别是 NOT_SUPPORTED(不支持当前事务;始终以非事务方式执行)
那么 不会开启事务,但是支持事务同步,并且会挂起当前事务,其中 prepareTransactionStatus
会初始化事务同步set,并且把当前事务的信息包装到 DefaultTransactionStatus
并返回 如果传播级别是 RequireNew(创建一个新事务,如果存在事务则暂停当前事务)
会挂起当前事务,从ThreadLocal中解绑,然后开启新事务,并初始化TransactionSynchronization的set ![](https://img2022.cnblogs.com/blog/2605549/202208/2605549-20220828200005644-13534481.png) 如果传播级别是
Nested(如果当前事务存在,则在嵌套事务中执行),首先会判断是否允许嵌套事务,如果不允许那么抛出异常,通常
DataSourceTransactionManager使用
savepoint来实现嵌套事务 ![](https://img2022.cnblogs.com/blog/2605549/202208/2605549-20220828200008245-813706094.png) 调用
Connection#setSavepoint方法 ![](https://img2022.cnblogs.com/blog/2605549/202208/2605549-20220828180249495-2022208573.png) 最后如果是
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
- 如果外部存在一个事务,且传播级别为嵌套事务,但是此时不是通过保存点来实现嵌套事务
Original: https://www.cnblogs.com/cuzzz/p/16633523.html
Author: Cuzzz
Title: Spring 源码学习笔记11——Spring事务
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/714265/
转载文章受原作者版权保护。转载请注明原作者出处!