【Spring源码核心篇-06】spring中事务的底层实现与执行流程
Spring源码核心篇整体栏目
内容 | 链接地址 |
---|---|
【一】Spring的bean的生命周期 | https://zhenghuisheng.blog.csdn.net/article/details/143441012 |
【二】深入理解spring的依赖注入和属性填充 | https://zhenghuisheng.blog.csdn.net/article/details/143854482 |
【三】精通spring的aop的底层原理和源码实现 | https://zhenghuisheng.blog.csdn.net/article/details/144012934 |
【四】spring中refresh刷新机制的流程和实现 | https://zhenghuisheng.blog.csdn.net/article/details/144118337 |
【五】spring中循环依赖的解决和底层实现 | https://zhenghuisheng.blog.csdn.net/article/details/144132213 |
【六】spring中事务的底层实现与执行流程 | https://zhenghuisheng.blog.csdn.net/article/details/144178500 |
spring中事务的底层实现与执行流程
- 一,spring事务扫描注册,执行和传播机制
- 1,Transactional注解的底层实现
- 1.1,EnableTransactionManagement事务开启
- 1.2,扫描全部advisor
- 1.3,匹配对应advisor
- 1.4,Transactional属性封装和解析
- 2,Transactional事务开启,提交,回滚
- 2.1,开启事务
- 2.1.1,dobegin执行事务开启
- 2.2,提交事务
- 2.3,回滚事务
- 3,总结
如需转载,请附上链接:https://blog.csdn.net/zhenghuishengq/article/details/144178500
一,spring事务扫描注册,执行和传播机制
在了解spring的事务之前,需要了解一些本人写的第三篇文章aop的底层原理和实现,因为spring事务是基于spring中的aop来实现的,spring事务底层也是会涉及先注册bean的后置处理器,然后扫描事务注解注册成一个advisor,后面通过pointcut进行匹配,匹配成功之后在创建动态代理去开启、提交、回滚事务等
1,Transactional注解的底层实现
接下来直接看spring中Transactional的底层是如何实现的,为什么加了注解就可以实现多条sql可以保证事务的原子性等。在使用这个Transactional事务注解时,需要通过一个注解@EnableTransactionManagement开启事务,接下来先分析这个 @EnableTransactionManagement 底层是如何实现的
1.1,EnableTransactionManagement事务开启
EnableTransactionManagement接口实现类如下,除了几个常规的自定义注解之外,还import导入了一个TransactionManagementConfigurationSelector类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
}
在这个TransactionManagementConfigurationSelector类中,其关系图如下,首先会实现一个AdviceModeImportSelector接口,然后实现了ImportSelector接口。当实现了ImportSelector接口的时候,在加载bean定义的时候会回调的selectImports方法
接下来查看这个TransactionManagementConfigurationSelector类下面的selectImports方法,里面会有两种模式,一种是PROXY代理模式,一种是ASPECTJ方式,在事务中,默认是使用上面这个PROXY模式
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
1.2,扫描全部advisor
此时会注册一个AutoProxyRegistrar类和ProxyTransactionManagementConfiguration类
- AutoProxyRegistrar会注册开启事务的beanPostProceed的bean的后置处理器,或许就可以去处理aop中对应的advisor,匹配对应的advice和pointcut
- ProxyTransactionManagementConfiguration里面会注册三个bean,分别是BeanFactoryTransactionAttributeSourceAdvisor、TransactionAttributeSource、TransactionInterceptor三个bean。BeanFactoryTransactionAttributeSourceAdvisor中就会对相应的advisor设置一些advice和pointcut,方便后续的advisor进行匹配
1.3,匹配对应advisor
BeanFactoryTransactionAttributeSourceAdvisor中设置的advice和pointcut,对应的就是另外两个bean,TransactionAttributeSource对应的就是pointcut切入点,TransactionInterceptor对应的就是advice增强器
上面设置的advice是很明显的可以看出是advisor中的增强器advice,但是设置setTransactionAttributeSource这个属性作为pointcut,因此就需要去内部查看,发现这个 TransactionAttributeSource 属性返回的就是一个 TransactionAttributeSourcePointcut 的pointcut的切入点
advisor.setTransactionAttributeSource(transactionAttributeSource());
上面注册了开启事务的的bean的后置处理器,然后为扫描每一个开启了事务的注解,全部扫描成advisor,最后会通过每一个注解上的pointcut对所有的advisor进行匹配,通过TransactionAttributeSourcePointcut类来实现匹配逻辑和规则。已经看这个TransactionAttributeSourcePointcut类中的matches方法中,也就是先匹配类,然后再匹配方法,最后通过getTransactionAttribute看匹配的方法上面是否有@Transactional注解。其事务逻辑就是aop的底层实现逻辑,当匹配成功后,那么就会创建动态代理区实现
1.4,Transactional属性封装和解析
接下来查看AbstractFallbackTransactionAttributeSource类中的getTransactionAttribute方法,上面是会找是否有@Trancational注解,那么这个实现类的方法中,就会去解析这些事务,比如会将这些事务解析其全部属性,然后将全部的属性封装到 TransactionAttribute 属性中,并且或许会对这些属性缓存
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
后序在解析这个对象时,就会获取所有的属性,会将所有的属性封装成一个 **RuleBasedTransactionAttribute ** 类,比如通过propagation属性解析事务的传播行为,通过isolation属性解析事务的隔离级别,通过value属性解析事务管理器的名称,通过rollbackFor属性解析事务的异常回滚等
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
//创建一个基础规则的事务属性对象
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
//解析我们@Transactionl上的传播行为
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
//解析我们@Transactionl上的隔离级别
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
//解析我们@Transactionl上的事务超时事件
rbta.setTimeout(attributes.getNumber("timeout").intValue());
rbta.setReadOnly(attributes.getBoolean("readOnly"));
//解析我们@Transactionl上的事务管理器的名称
rbta.setQualifier(attributes.getString("value"));
//解析我们的针对哪种异常回滚
List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
//对哪种异常进行回滚
for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
//对哪种异常不回滚
for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
//对哪种类型不回滚
for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
rbta.setRollbackRules(rollbackRules);
return rbta;
}
2,Transactional事务开启,提交,回滚
在1中重点讲了在spring中事务的是如何实现,如何注册bean的后置处理器,扫描bean成advisor以及后期pointcut的匹配,接下来需要继续通过源码分析在spring中低如何开启事务,提交事务和回滚事务的
接下来继续回到上面的TransactionInterceptor这个类,就是用于advice增强的,当pointcut切入点匹配成功之后,会通过这个类创建动态代理,内部通过调用invoke方法实现,在该方法中会先拿到代理类,然后执行事务回调方法 invokeWithinTransaction
接下来就是进入这个核心的方法 invokeWithinTransaction 中,其具体实现如下
在这个方法中,首先第一步就是获取事务属性对象,在上面1.4的时候就已经将全部匹配的对象都,封装成了这个 TransactionAttributeSource 对象
//获取我们的事务属源对象 在配置类中添加的
TransactionAttributeSource tas = getTransactionAttributeSource();
// 获取解析后的事务属性信息,
// 创建代理的时候也调用了getTransactionAttribute还记得吗, 如果解析到了事务属性就可以创建代理,
// 在这里是从解析后的缓存中获取
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
随后就是获取自定义配置的事务管理器的对象,后序创建、提交、回滚事务等都是通过这个对象进行操作的
// 获取配置的事务管理器对象
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
最后就是获取一个切入点,就是获取方法的名称,这里方法的名称就是事务的名称。也可以通过一些api去验证当前事务的名字到底是什么
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
接下来这行代码就是判断是否需要开启一个事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
2.1,开启事务
接下来查看这个 createTransactionIfNecessary 方法,看spring底层是如何开启一个事务的。
- 首先会判断这个事务是否为空或者事务的名字是否为空,如果事务不为空但是名字为空,就是如我们正常开发只加一个@Transational注解,没有设置事务名称,那么就会将加在对应方法名称作为事务名称
- tm.getTransaction 这段方法会去开启以一个事务,最终会得到一个TransactionStatus对象
接下来查看AbstractPlatformTransactionManager实现类的getTransaction方法,随后调用真正执行doGetTransaction获取事务的方法,每一个数据源对象都是新创建的,随后会resource的ThreadLocal数据源中获取这个事务,返回一个ConnectionHolder对象
其他逻辑如下,判断传进来的属性是否为空,要创建的事务是否已经存在,事务的超时时间是否过时等。因为所有新创建的事务都会加入到一个resource的ThreadLocal副本变量中,因此要知道事务是否已经存在,那么只需要去这个ThreadLocal中获取即可
然后继续往下走,有一段传播机制的设置,首先判断事务的传播机制是不是 PROPAGATION_MANDATORY 类型的,表示事务是必须开启的,如果当前事务不存在,那么就会直接抛异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
当然如果事务的传播机制是 PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED 这三种数据类型,后面会会调用一个 suspend 方法挂起当前事务的操作,这个挂起操作主要是针对传播机制的,在后面再讲。随后调用一个doBegin方法开启一个事务,然后讲事务线程绑定到线程副本变量中
2.1.1,dobegin执行事务开启
接下来就是真正的开启事务的方法doBegin方法,接下来查看这个dobegin到底是如何开启的,首先就是获取到数据库的Connection连接,然后将数据库连接包装成一个ConnectionHolder对象里面,随后就是设置隔离级别。这里调用setConnectionHolder方法时的第二个参数设置为true,表示数据源是第一次连接
接着上面的代码继续,会设置是否手动提交,设置事务是否为只读事务,设置超时时间,最后将数据源绑定到同步管理器上面,将数据源datasource作为key,数据库的连接Connection作为value
也就是说会将数据源和数据库连接作为key-value,设置到线程ThreadLocal中,通过上面的bindResource方法可以定位到这个resources,就是一个ThreadLocal线程
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
2.2,提交事务
依旧得回归到这个 invokeWithinTransaction 方法,在事务创建之后,接下来查看这个提交事务是如何实现的
接下来直接看这个 commitTransactionAfterReturning 方法,重点是这个commit方法
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
在这个commit提交的代码中,会对一些属性进行判断,如有没有手动回滚之类的,没有的话就会执行processCommit进行真正的提交。
最后就是真正的执行这个commit提交的操作,首先会执行一些预提交的操作,最后真正的执行doCommit的操作,通过调用底层的jdbc这些进行提交。这里进行提交前会做一个判断,就是判断当前的status是不是一个新建的事务,就比如3里面的这个例子,在insert这个加了事务的方法里面又调了一个加了事务的build方法,在执行build方法的时候,那么这个事务就不是新建的,而是insert的事务,因此也不会执行这个doCommit提交的方法
在提交之后,会执行一个cleanupAfterCompletion方法,主要用于恢复被挂起的线程,让被挂起的线程获得资源。如在出现事务传播属性的时候,就可能挂起前一个事务,那么在这里就是用于恢复这个被挂起的事务,会通过调用resume方法实现,将之前挂起时所封装的对象恢复成一个事务
cleanupAfterCompletion();
最后通过执行doResume方法获取挂起事务的对象,并将一些事务的隔离级别、名称等也设置进去
2.3,回滚事务
依旧得回归到这个 invokeWithinTransaction 方法,在事务创建之后,接下来查看这个回滚事务是如何实现的
接下来直接查看这个 completeTransactionAfterThrowing 方法,比如说在执行sql的时候抛了异常,那么就会执行这个回滚的方法,回滚方法流程如下:
- 由于在设置rollback时,可以设置在什么异常类下面进行回滚,因此第一步会判断抛的异常是否属于设置的这个类的数据类型,如判断是否是空指针类型这种,不属于则直接提交,提交事务就走上面2.2的流程
- 如果抛的异常时设置的回滚类型,那么就会直接的调用这个rollback进行回滚
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
//判断抛出的异常是不是我们需要回滚的异常
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
//回滚
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
}
}
else { //不是
try {
//提交事务
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
}
}
}
}
在rollback方法中,主要有一个核心方法processRollback,用于执行回滚
在这个 processRollback 方法中,也是会和提交一样,判断当前事务是不是一个新建的事务,如果是的话那么会执行真正的 doRollback 方法,底层也比较简单,就是调用jdbc的rollback回滚方法进行回滚
最后也会和commit提交方法一样,对有挂起的事务进行恢复,会通过调用resume方法实现,将之前挂起时所封装的对象恢复成一个事务
cleanupAfterCompletion(status);
3,总结
spring事务主要是通过 EnableTransactionManagement 注解开启,开启之后会注册一个bean的后置处理器,去扫描所有加了@Trancational的类或者方法,利用spring的aop将这些bean全部注册成一个advisor,最后进行对每一个advisor的pointcut进行匹配,先匹配类和方法,再匹配pointcut,匹配成功之后创建动态代理去加载代理类,从而执行去开启一个事务,将事务的datasource数据源和Connection连接存ThreadLocal中,然后设置一些事务的名称,隔离级别,传播属性等,最后底层调用jdbc的commit进行提交或者rollback进行回滚