🍃Spring源码阅读-事务
type
status
date
slug
summary
tags
category
icon
password
原文
声明式工作流程简述1.启用事务2.注解扫描注解的优先级3.创建动态代理对象事务方法的执行1. 事务方法的触发传播行为2. 事务传播行为的处理挂起事务和恢复事务2.开始事务3.事务的提交声明是事务失效的场景1. 被调用的方法不是 public修饰的 2. 方法用 final 或 static 修饰3. 对象没有被Spring 管理4. 表不支持事务5. 方法内部调用如何解决该问题6. 未开启事务7. 吞掉异常8.使用@Transactional方式错误
spring 事务实现涉及动态代理、AOP拦截器、事务管理器(PlatformTransactionManager)以及底层数据库(jdbc)的协同工作。
spring IOC 容器初始化时,会识别是否启用事务,如果启用,容器会在处理容器内部组件加入
事务相关的容器 Bean(
BeanFactoryTransactionAttributeSourceAdvisor
、TransactionInterceptor
等),在 Bean 初始化后置处理中将 Bean以代理对象替换原有对象,创建代理流程大致同 AOP创建代理对象一致,只是带事务的代理Bean所持有的对象有所不同。这里展示了一个Jdk动态代理的一个 Service,其中拦截链路advisors 中相关的对象是事务相关的Bean。
AOP的处理流程也有 advice(切点和切面)、MethodInterceptor(多种 Before、after 等),在处理事务的代理 Bean 中,这里不过是替换成事务需要的 advice,MethodInterceptor(TransactionInterceptor)。
声明式工作流程简述
1.启用事务
当使用
@EnableTransationManagement
注解启动事务时,Spring 通过TransationManagementConfiguraigurationSelector
导入两个核心组件:AutoProxyRegistrar
:获取@EnableTransationManagement
注解中的配置,注册InfrastructureAdvisorAutoProxyCreator
,负责为 Bean 创建 AOP 代理。
在Spring IOC容器初始化代理创建器只会存在一个名为
org.springframework.aop.config.internalAutoProxyCreator
的代理创建器,事务、切面等都会触发IOC组件初始化的操作。如果应用同时开启了事务、AOP,Spring IOC 容器会在初始化内部组件时(refresh()
#
egisterBeanPostProcessors
)会有重复注册内部组件的逻辑,如果有已经创建的代理创建器会根据优先级进行替换。ProxyTransactionManagementConfiguration
:配置事务拦截器和切面,定义相关Bean(如TransactionInterceptor
和BeanFactoryTransactionAttributeSourceAdvisor)
BeanFactoryTransactionAttributeSourceAdvisor
事务的 Advisor,也就是封装了事务的拦截逻辑,应用到带有@
Transactional
的方法上。和AOP Advisor 功能类似,只不过事务的切面有所不同,事务只有用户只会定义Pointcut,通知(advice)后执行由具体的事务的拦截器TransactionInterceptor
来决定,而 AOP 是用户自定义的逻辑TransactionInterceptor
负责在方法调用前后自动开启、提交或回滚事务。
ProxyTransactionManagementConfiguration
是一个带有 @Configuration 注解的配置类,其作用是注册启用注解驱动事务管理所需的 Spring 基础设施 Bean。声明式事务也是一种切面,
ProxyTransactionManagementConfiguration
会封装关于事务切面的几个核心类2.注解扫描
AnnotationTransactionAttributeSource
扫描哪些类、方法上持有 @Transactional
注解,并将其转换为事务属性(TransactionAttribute
), TransactionAttribute
是描述目标类和方法上的@Transactional
注解的属性(见TransationalAttribute示例)。这一步是在Bean 初始化后置处理阶段做这些逻辑处理。
TransactionAttribute
示例注解的优先级
@Transational注解的解析优先级:
- 方法上注解:优先使用方法上的@Transational注解配置
- 类级别注解:若方法上没有注解,则使用类上的注解
- 接口注解:若通过 jdk动态代理,接口上的注解也会被解析(需配置
@EnableTransactionManagement(proxyTargetClass = false)
)
3.创建动态代理对象
与 Spring 创建动态代理对象流程一致,只不过部分包装代理对象切面、切点、拦截器是事务相关的。这里说的动态代理对象指的是包含
@
Transactional
的Bean创建代理对象。如下定义了一个 Service,容器会创建一个Bean name 为 sysUserServiceImpl 的代理对象。(这里是开启事务的情况下创建代理对象的,即使没有开启 AOP)事务方法的执行
在代理对象执行相关事务的方法时,会触
TransactionInterceptor#invokeWithinTransaction
方法,在这个方法中会获取方法上注解上事务属性,然后去创建事务(createTransactionIfNecessary
),”事务“相关的行为(事务信息处理、传播行为等)准备好后,去执行目标方法,根据目标方法的执行结果和是否抛出异常然后执行对应的操作(commit 或 rollback)。1. 事务方法的触发
一个 Service Bean 已经是代理对象,以 Jdk动态代理为例,一个
JdkDynamicAopProxy
包装的Service Bean,在执行其方法时触发的是
JdkDynamicAopProxy#invoke
方法,通过事务 Bean 的拦截器是 TransactionMethoder
,这里会触发TransactionAspectSupport@invokeWithinTransaction
方法的调用,整个事务的生命周期都在处理。final TransactionManager tm = determineTransactionManager(txAttr);
这里是获取事务管理器,Spring 支持两种事务管理器同步和Reactive类型
传播行为
- 以非事务方式执行的
NOT_SUPPORTED 没有就非事务方式执行,有就直接挂起,然后非事务方式执行。
NEVER 没有就非事务执行,有就抛出异常
- 可有也可无事务方式执行的
- 和REQUIRES_NEW的区别
- 和REQUIRED的区别
SUPPORTS 有就用 没有则以非事务的方式执行的
REQUIRED(默认方式)如果没有,就新建一个事务,如果有就加入当前事务,最常用的一种。MANDATORY 如果没有就抛出异常。如果有,就使用当前事务。
REQUIRES_NEW 有没有都事务都会新建事务,如果原来有就将原来的事务挂起。该传播级别下,事务之间不会相互影响
NESTED 如果没有,就新建一个事务;如果有,就再当前事务嵌套其他事务。
REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而
NESTED则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务。
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚 而在
NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响
2. 事务传播行为的处理
前面提到的
createTransactionIfNecessary
方法,最终调用的是
AbstractPlatformTransactionManager#getTransaction
这里面包含了事务信息的处理、开启事务,事务和线程的绑定、以及传播行为处理等逻辑。这里的代码处理传播行为、获取数据库连接、开启事务、事务和线程绑定的逻辑等。
isExistingTransaction()
:如果目标对象的事务方法包含了其他代理的对象事务方法,在第二次调用该方法时会进入该分支。如下示例事务的传播行为REQUIRED、REQUIRES_NEW、NESTED、NOT_SUPPORTED,这四种行为会引发挂起当前事务,用于和外层事务隔离。挂起的事务会被封装成
SuspendedResourcesHolder
实例存储在TransactionStatus
实例中在方法调用栈中进行传递。在新事务执行完毕时,会从TransactionStatus
中取出被挂起的事务继续执行。
事务的传播行为定义了是否开启事务、挂起和参与逻辑,如REQUIRES_NEW,作为独立事务存在,它是需要开启新事务,它会在新的数据库的连接中去完成自己的事务。事务的挂起是解绑当前的数据库连接,防止新事务重复用同一连接。
挂起事务和恢复事务
DataSourceTransactionObject
为当前事务的对象,它有一个ConnectionHolder对象属性,它维持着数据库连接信息。挂起事务是将当前事务持有的连接置null,以及事务的资源解绑。
恢复事务就是使用之前挂起事务的
SuspendedResourcesHolder
实例,将事务的状态恢复。之前挂起的事务将事务持有的 connection 置为null,但是恢复事务的时候并没有获取数据库连接与之绑定,只是将事务资源(数据库连接池工厂对象)绑定到事务。Spring 支持多个数据源场景下的事务管理。每个 DataSource 可以对应不同的数据库实例或连接池。使用 DataSource 作为资源进行绑定,可以区分不同数据源的事务上下文。同一线程内,可以通过不同的 DataSource 管理多个独立的事务资源。2.开始事务
在前面的步骤以后,事务的传播行为、事务的上下文信息都已经处理完成后,接着就要准备获取数据库连接准备开始事务的操作。到这一步,事务的已经开启,根据事务的上下文信息构建一个
TransactionInfo
实例,接着开始执行目标的事务方法。
从代理对象执行 invoke 方法开始,到这一步可以简单的总结为
- 构建事务上下文信息
- 事务的资源绑定
- 事务的基础信息处理
- 事务的传播行为处理
- 决定是否挂起事务
- 获取数据库连接
- 开启事务
3.事务的提交
执行目标事务方法以后,根据执行是否抛出异常,决定事务的提交和回滚,到这里事务方法的执行就结束了。
声明是事务失效的场景
1. 被调用的方法不是 public修饰的
在Bean初始化后,创建代理对象时,会判断Bean上的目标方法上提取@Transactional注解,构建
TransactionAttribute
对象时,会判断目标方法的修饰符,如果是非 public 的是会构建当前方法的事务属性。2. 方法用 final 或 static 修饰
如果事务方法用final修饰,将会导致事务失效。Spring 事务底层使用aop,也就是通过jdk动态代理或cglib,帮我们生成了代理类,在代理中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中 ,就无法重写该方法而添加事务功能。
同理如果某个方法是static的,同样无法通过动态代理,变成事务方法。
3. 对象没有被Spring 管理
2. 在不属于spring Bean管理的类中使用@Transactional
使用Spring事务的前提是:对象要被Spring的管理,需要创建bean实例。如果类没有加@Controller,
@Service @Component @ Repository 等注解,即该类没有交给Spring管理,那么它的方法也不会生成事务。
4. 表不支持事务
如果使用的 MySQL,只有 Inndb 支持事务。
5. 方法内部调用
示例说明: 在updateOrder方法上的事务会失效,因为发生了自身的调用,调用该类的自己的方法,而没有经过Spring的代理类,只有外部调用的事务才会生效。
如何解决该问题
- 再申明一个Service,由内部调用变成外部调用。
- 使用编程式事务
- 使用
AopContext.currentProxy()
方法获取代理对象
6. 未开启事务
如果是Spring项目,则需要手动配置事务相关的参数,如果忘记配置参数,则事务是不会生效的。
如果是SpringBoot项目,那么是不需要手动配置,因为SpringBoot已经在
DataSourceTransactionManagerAutoConfiguration
类中帮我们开启事务。7. 吞掉异常
有些时候事务不会回滚,有可能是代码中手动catch了异常。开发者在在事务方法中catch了异常,也没有手动抛出,这种情况下Spring的事务是不会回滚的。
如果想要Spring能够正常回滚,必须抛出它能够处理的异常。
8.使用@Transactional方式错误
@Transactional注解的方法,默认是抛出RuntimeException,如果方法中抛出例如ParseException则无法处理。
Excetion
- ---RuntimeException
- ---ParseException
该种情况引发事务失效的情况比较隐蔽,在第7点中
提到如果想要Spring能够正常的回滚,必须抛出它能处理的异常
,Transactional中抛出RuntimeException,而ParseException不属于RuntimeException,所以事务无法正确回滚。Prev
Spring源码阅读-AOP
Next
Session、Cookie、Token
Loading...