当前位置: 首页 > article >正文

【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进行回滚


http://www.kler.cn/a/420589.html

相关文章:

  • 实时数据开发 | Flink的数据分区策略--物理分区操作
  • Stable Diffusion 3详解
  • 【Markdown编辑器】
  • 下载 M3U8 格式的视频
  • 【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(6)
  • 网络安全(三):网路安全协议
  • vue.js学习(day 13)
  • C基础练习题
  • Ubuntu22.04上kdump和crash的使用
  • D83【python 接口自动化学习】- pytest基础用法
  • 一键生成数据库对应的所有DataX的json文件
  • mvc基础及搭建一个静态网站
  • Ubantu系统docker运行成功拉取失败【成功解决】
  • GateWay使用手册
  • 清理Linux/CentOS7根目录的思路
  • Vue3 脚手架扩展
  • Proteus8.17下载安装教程
  • MySQL安装部署
  • IP划分(笔记)
  • 对于Oracle来说,土地管理是非核心域吗
  • 【机器学习】机器学习的基本分类-监督学习-逻辑回归-对数似然损失函数(Log-Likelihood Loss Function)
  • Apache-HertzBeat开源实时监控系统存在默认口令漏洞
  • mysql一个事务最少几次IO操作
  • ESP32开发板在micropython里直接用requests向web服务器发送请求:ESP32S3开发板通过fastapi中转成功连接星河大模型
  • 负载均衡指南:Nginx与HAProxy的配置与优化
  • mysql 查询所有的触发器