【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中事务的底层实现与执行流程

如需转载,请附上链接: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类

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
    
}

在这个TransactionManagementConfigurationSelector类中,其关系图如下,首先会实现一个AdviceModeImportSelector接口,然后实现了ImportSelector接口。当实现了ImportSelector接口的时候,在加载bean定义的时候会回调的selectImports方法

接下来查看这个TransactionManagementConfigurationSelector类下面的selectImports方法,里面会有两种模式,一种是PROXY代理模式,一种是ASPECTJ方式,在事务中,默认是使用上面这个PROXY模式

java 复制代码
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的切入点

java 复制代码
advisor.setTransactionAttributeSource(transactionAttributeSource());

上面注册了开启事务的的bean的后置处理器,然后为扫描每一个开启了事务的注解,全部扫描成advisor,最后会通过每一个注解上的pointcut对所有的advisor进行匹配,通过TransactionAttributeSourcePointcut类来实现匹配逻辑和规则。已经看这个TransactionAttributeSourcePointcut类中的matches方法中,也就是先匹配类,然后再匹配方法,最后通过getTransactionAttribute看匹配的方法上面是否有@Transactional注解。其事务逻辑就是aop的底层实现逻辑,当匹配成功后,那么就会创建动态代理区实现

1.4,Transactional属性封装和解析

接下来查看AbstractFallbackTransactionAttributeSource类中的getTransactionAttribute方法,上面是会找是否有@Trancational注解,那么这个实现类的方法中,就会去解析这些事务,比如会将这些事务解析其全部属性,然后将全部的属性封装到 TransactionAttribute 属性中,并且或许会对这些属性缓存

java 复制代码
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);

后序在解析这个对象时,就会获取所有的属性,会将所有的属性封装成一个 **RuleBasedTransactionAttribute ** 类,比如通过propagation属性解析事务的传播行为,通过isolation属性解析事务的隔离级别,通过value属性解析事务管理器的名称,通过rollbackFor属性解析事务的异常回滚等

java 复制代码
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 对象

java 复制代码
//获取我们的事务属源对象 在配置类中添加的
TransactionAttributeSource tas = getTransactionAttributeSource();
// 获取解析后的事务属性信息,
//  创建代理的时候也调用了getTransactionAttribute还记得吗, 如果解析到了事务属性就可以创建代理,
//  在这里是从解析后的缓存中获取
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);

随后就是获取自定义配置的事务管理器的对象,后序创建、提交、回滚事务等都是通过这个对象进行操作的

java 复制代码
// 获取配置的事务管理器对象
final PlatformTransactionManager tm = determineTransactionManager(txAttr);

最后就是获取一个切入点,就是获取方法的名称,这里方法的名称就是事务的名称。也可以通过一些api去验证当前事务的名字到底是什么

java 复制代码
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

接下来这行代码就是判断是否需要开启一个事务

java 复制代码
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 类型的,表示事务是必须开启的,如果当前事务不存在,那么就会直接抛异常

java 复制代码
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线程

java 复制代码
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");

2.2,提交事务

依旧得回归到这个 invokeWithinTransaction 方法,在事务创建之后,接下来查看这个提交事务是如何实现的

接下来直接看这个 commitTransactionAfterReturning 方法,重点是这个commit方法

java 复制代码
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方法实现,将之前挂起时所封装的对象恢复成一个事务

java 复制代码
cleanupAfterCompletion();

最后通过执行doResume方法获取挂起事务的对象,并将一些事务的隔离级别、名称等也设置进去

2.3,回滚事务

依旧得回归到这个 invokeWithinTransaction 方法,在事务创建之后,接下来查看这个回滚事务是如何实现的

接下来直接查看这个 completeTransactionAfterThrowing 方法,比如说在执行sql的时候抛了异常,那么就会执行这个回滚的方法,回滚方法流程如下:

  • 由于在设置rollback时,可以设置在什么异常类下面进行回滚,因此第一步会判断抛的异常是否属于设置的这个类的数据类型,如判断是否是空指针类型这种,不属于则直接提交,提交事务就走上面2.2的流程
  • 如果抛的异常时设置的回滚类型,那么就会直接的调用这个rollback进行回滚
java 复制代码
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方法实现,将之前挂起时所封装的对象恢复成一个事务

java 复制代码
cleanupAfterCompletion(status);

3,总结

spring事务主要是通过 EnableTransactionManagement 注解开启,开启之后会注册一个bean的后置处理器,去扫描所有加了@Trancational的类或者方法,利用spring的aop将这些bean全部注册成一个advisor,最后进行对每一个advisor的pointcut进行匹配,先匹配类和方法,再匹配pointcut,匹配成功之后创建动态代理去加载代理类,从而执行去开启一个事务,将事务的datasource数据源和Connection连接存ThreadLocal中,然后设置一些事务的名称,隔离级别,传播属性等,最后底层调用jdbc的commit进行提交或者rollback进行回滚

相关推荐
李慕婉学姐11 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆13 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin13 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model200513 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉13 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国13 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_9418824814 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈14 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_9914 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹14 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理