目录

  • 一、说明
  • 二、TransactionInterceptor开启事务
    • (1)、拦截方法
    • (2)、开启事务绑定数据库连接
    • (3)、mybatis中sql执行数据库连接获取
    • (4)、事务提交和当前线程ThreadLocal清理,sqlSession关闭
  • 三、总结

一、说明

接着上一个博客SpringBoot 声明式事务 源码解析,下面看一下事务开启后把当前数据库连接绑定到ThreadLocal中,mybatis执行数据库操作时,从ThreadLocal中获取连接执行sql,最后拦截器提交或回滚事务,执行sqlsession(一个sqlsession对应一个数据库连接)提交或回滚,然后清理ThreadLocal,关闭sqlsession,数据库连接回收到数据库连接池。

二、TransactionInterceptor开启事务

(1)、拦截方法

	@Override@Nullablepublic Object invoke(MethodInvocation invocation) throws Throwable {// Work out the target class: may be {@code null}.// The TransactionAttributeSource should be passed the target class// as well as the method, which may be from an interface.Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);}

执行invokeWithinTransaction方法,如下图,1是获取目标方法上配置的隔离级别和传播属性等属性,2是开启事务的具体方法,3是继续执行后续拦截器最终执行目标方。
在这里插入图片描述
在这里插入图片描述

下面重点看2方法中的内容。

继续看status = tm.getTransaction(txAttr);
在这里插入图片描述

(2)、开启事务绑定数据库连接

下面跟踪方法时,重点看开启事务和绑定数据库连接到TreadLocal中的内容。
在看代码之前,先看看自动配置类中创建了DataSourceTransactionManager组件,不明白什么时候创建的可以看一下我上个博客。组件中放入了dataSource数据源,若依的框架中添加了动态数据源的配置。
在这里插入图片描述
在这里插入图片描述
下图是status = tm.getTransaction(txAttr);方法中开启事务的内容。doBegin方法时绑定数据库连接到当前线程。doBegin下面prepareSynchronization里面也很重要,记录一下往TransactionSynchronizationManager中设置了许多参数后面会用到,看一下 TransactionSynchronizationManager.initSynchronization();
在这里插入图片描述
下图第一个箭头设置了ActualTransctionActive=true在提交事务的时候会用到。往synchronizations放入了空集合,synchronizations是一个ThreadLocal。
在这里插入图片描述
在这里插入图片描述
下面看一下doBegin方法。
在这里插入图片描述
下面是从数据源中获取连接,把连接设置到了txObject的ConnectionHolder数据库连接描述对象中,后续还会从这里面取出。设置了newConnectionHolder=true.
在这里插入图片描述
1、先判断连接是不是自动提交,如果是自动提交会设置成不可以自动提交。2、把事务可用状态设置成true,后续会用到。3、把当前连接信息绑定到当前线程。
在这里插入图片描述
可以看到resources是ThreadLocal,ThreadLocal中放入了Map集合,key是动态数据源,value是数据库连接描述对象ConnectionHolder。
在这里插入图片描述
在这里插入图片描述

(3)、mybatis中sql执行数据库连接获取

MybatisAutoConfiguration中会自动注入SqlSessionTemplate组件,@MapperScan中引入了ClassPathMapperScanner组件,组件扫描所有mapper.java文件,把接口设置成MapperFactoryBean类型的组件,可以一下我以前的博客Spring如何管理Mapper,在设置bean的描述时,也设置了SqlSessionTemplate组件到Mapper中,执行到Configuration.addMapper时,knownMappers.put(type, new MapperProxyFactory(type));往map中设置了MapperProxyFactory,当Configuration.getMapper时,会调用MapperProxyFactory生成代理类MapperProxy,默认使用的是jdk动态代理。综上所述,当执行mapper的update方法时,会到MapperProxy代理方法invoke。后面会执行MapperMethod中执行execute方法。
在这里插入图片描述
会执行SqlSessionTemplate中的update方法。如下图当SqlSessionTemplate创建的时候,会设置SqlSessionTemplate的代理类sqlSessionProxy,SqlSessionInterceptor是代理类的拦截方法。执行SqlSessionTemplate中的update方法会进入SqlSessionInterceptor的invoke方法。

在这里插入图片描述
从if判断可以看到如果没有开启事务,sqlSession会提交事务。如果开启了,使用事务拦截器统一提交事务。
在这里插入图片描述
看一下if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory))内容,如果ThreadLocal中已经存入了sqlSession并和当前的sqlSession是同一个说明开启了事务,使用事务拦截器提交。

在这里插入图片描述

分析一下getSqlSession方法,主要是获取sqlSession,先进入getSqlSession方法看ransactionSynchronizationManager.getResource(sessionFactory);

在这里插入图片描述

从resources中获取以sessionFactory为key,值是defaultSqlSession的map,其中resources是ThreadLocal。第一次执行map是null。
在这里插入图片描述
请看源码,我添加了注释

  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);//从ThreadLocal中获取SqlSessionHolder,可以通过SqlSessionHolder获取生成的sqlSessionSqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);//从SqlSessionHolder中获取sqlSession,如果获取都会直接返回SqlSession session = sessionHolder(executorType, holder);if (session != null) {return session;}//通过sessionFactory和执行器类型创建sqlSessionLOGGER.debug(() -> "Creating a new SqlSession");session = sessionFactory.openSession(executorType);//把创建好的sqlSession,放入到ThreadLocal中,只有开启事务才能放入registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;}

看一下openSession代码, tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);//1、创建了SpringManagedTransaction,传入数据源参数tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//2、创建执行器,传入了SpringManagedTransactionfinal Executor executor = configuration.newExecutor(tx, execType);//3、创建DefaultSqlSession,传入了执行器executor return createSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

会创建Transaction类型的组件SpringManagedTransaction,传入了动态数据源。

 public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {return new SpringManagedTransaction(dataSource);}

继续往下跟踪到registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);代码如下在这里插入图片描述
1、里面代码判断synchronizations是不是null,这里上面提到过,在TranscationIntercetor中开启事务后,prepareSynchronization方法中设置了空集合,所以这里是TransactionSynchronizationManager.isSynchronizationActive()=true,synchronizations也是TreadLocal避免了线程不安全问题。

在这里插入图片描述

2、创建了SqlSessionHolder其中包含创建好的DefaultSqlSession。
3、往ThreadLocal中放入了Map,map的key是sessionFactory,value是2中创建的SqlSessionHolder。开启事务后,执行后面的sql可以从ThreadLocal取出。
在这里插入图片描述

4、创建了SqlSessionSynchronization其中包含创建好的sqlsession,放入到了集合中,此集合会放入1中synchronizations中,后面提交事务的时候会用到。
在这里插入图片描述
5、设置了SqlSessionHolder中SynchronizedWithTransaction=true。
总上所述,当开启事务后,在同一个事务中,使用mybatis执行多个sql时,会重复使用同一个DefaultSqlSession,(DefaultSqlSession被绑定到了线程中),也会使用同一个数据库连接,保证可以使用事务。如果没有开启事务,每次执行sql都会重新创建一个DefaultSqlSession。事务的开启和提交回滚都是mybatis来负责的。

继续回到MapperMethod.execute->sqlSession.update(command.getName(), param)
->executor.update(ms, wrapCollection(parameter));
->BaseExecutor.update
->SimpleExecutor.doUpdate
->prepareStatement(handler, ms.getStatementLog());
->Connection connection = getConnection(statementLog);–>openConnection();
-> this.connection = DataSourceUtils.getConnection(this.dataSource);
->doGetConnection 如下代码可以看到从ThreadLocal里面获取map使用动态数据源做key,获取数据库连接描述对象,这个数据库连接对象在开启事务后放入的,可以找找上面的内容有提到。SpringManagedTransaction类中
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

public static Connection doGetConnection(DataSource dataSource) throws SQLException {Assert.notNull(dataSource, "No DataSource specified");ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {conHolder.requested();if (!conHolder.hasConnection()) {logger.debug("Fetching resumed JDBC Connection from DataSource");conHolder.setConnection(fetchConnection(dataSource));}return conHolder.getConnection();}// Else we either got no holder or an empty thread-bound holder here.logger.debug("Fetching JDBC Connection from DataSource");Connection con = fetchConnection(dataSource);

在这里插入图片描述
获取数据库连接后会设置到SpringManagedTransaction类中的connection属性中,下面看一下SpringManagedTransaction类关系。

在这里插入图片描述
每次创建sqlsession的时候会先都会创建SpringManagedTransaction,SpringManagedTransaction当获取数据库连接后会设置到本类的属性connection上,创建执行器Excutor是会把SpringManagedTransaction设置进去,然后把Excutor设置到sqlsession中。这可以理解为同一个sqlsession对应同一个数据库连接java.sql.Connection。
在这里插入图片描述

(4)、事务提交和当前线程ThreadLocal清理,sqlSession关闭

定位到TransactionAspectSupport.invokeWithinTransaction方法,方法内开启事务,执行拦截器和目标方法最后提交事务。下面看看提交事务方法。
commitTransactionAfterReturning(txInfo);
在这里插入图片描述

txInfo.getTransactionManager().commit(txInfo.getTransactionStatus())
->processCommit(defStatus);
->triggerBeforeCompletion
-> TransactionSynchronizationUtils.triggerBeforeCompletion();
遍历TransactionSynchronization执行beforeCompletion

	for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {try {synchronization.beforeCompletion();}catch (Throwable tsex) {logger.error("TransactionSynchronization.beforeCompletion threw exception", tsex);}}
  public void beforeCompletion() {// Issue #18 Close SqlSession and deregister it now// because afterCompletion may be called from a different threadif (!this.holder.isOpen()) {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");}//删除ThreadLocal中绑定的sessionFactory,和sqlSession
TransactionSynchronizationManager.unbindResource(sessionFactory);this.holderActive = false;if (LOGGER.isDebugEnabled()) {LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");}//关闭sqlSessionthis.holder.getSqlSession().close();}}

主要看一下 TransactionSynchronizationManager.unbindResource(sessionFactory);清理绑定的sessionFactory和sqlSession的map集合

public static Object unbindResource(Object key) throws IllegalStateException {Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);Object value = doUnbindResource(actualKey);if (value == null) {throw new IllegalStateException("No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");}return value;}
	private static Object doUnbindResource(Object actualKey) {Map<Object, Object> map = resources.get();if (map == null) {return null;}Object value = map.remove(actualKey);// Remove entire ThreadLocal if empty...if (map.isEmpty()) {resources.remove();}// Transparently suppress a ResourceHolder that was marked as void...if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {value = null;}if (value != null && logger.isTraceEnabled()) {logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +Thread.currentThread().getName() + "]");}return value;}

最终提交事务,方法在processCommit->triggerBeforeCommit->TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly())遍历所有的TransactionSynchronization 类型的组件前面添加过SqlSessionSynchronization其中包含了创建好的sqlsession

public static void triggerBeforeCommit(boolean readOnly) {for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {synchronization.beforeCommit(readOnly);}}

获取当前线程的sqlsession,执行提交操作。可以看一下if条件,就是开启事务后在prepareSynchronization方法中设置的ThreadLocal类型的属性actualTransactionActive中是true。

在这里插入图片描述
执行到sqlsession中执行器的commit,设置不能自动提交。
在这里插入图片描述
继续执行执行器中的SpringManagedTransaction中的commit,SpringManagedTransaction在创建sqlsession的时候提到了,
在这里插入图片描述
最终获取SpringManagedTransaction中的connection,进行事务提交。
在这里插入图片描述

三、总结

1、TransactionInterceptor拦截到目标方法开启事务设置第一个ThreadLocal放入数据源为key,数据库连接描述类为value的map集合。
2、执行mybatis的sql时,sqlSession中的excutor中SpringManagedTransaction类会从第一个ThreadLocal中根据动态数据源取出相应的数据库连接执行sql,保证了开启的事务和执行sql同一个数据库连接。
3、在mybatis的sqlSessionTemplate执行增删改方法时,sqlSession的代理类SqlSession执行getSqlSession,如果开启了事务,出现第二个ThreadLocal,里面存放以sqlSessionFactory为key,defaultSqlSession为value的map集合,如何在同一个事务中,执行每个sql,defaultSqlSession会使用同一个。如果没有开启事务第二个ThreadLocal不生效,每次执行sql都会创建一次defaultSqlSession。事务的开启和提交都是mybatis控制的。
为何开启事务后,在事务中每个mapper增删改查操作都使用同一个sqlsession呢?因为 MyBatis 的 SqlSession 在设计上就是数据库连接(java.sql.Connection)的一个高级封装和门面(Facade)对象。一个 SqlSession 实例在其生命周期内,内部始终持有且仅持有一个 Connection 对象,同一个 SqlSession 就是同一个数据库连接,提交事务时,多个方法使用同一个SqlSession提交方法进而同一个数据库连接提交,保证了事务一致性
一个 SqlSession 实例 → 包含一个 Executor 实例 → 持有一个 Transaction 对象 → 管理一个唯一的 Connection 对象。
4、当事务提交成功或回滚时,会自动清理掉两个ThreadLocal中当前线程中的数据关闭SqlSession,回收 Connection 对象到线程池。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/95135.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95135.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/95135.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

05.《ARP协议基础知识探秘》

ARP协议基本介绍与实践 文章目录**ARP协议基本介绍与实践**ARP概述ARP报文类型ARP工作过程解析ARP工作原理示意图无故ARP/免费ARP实验案例**实验目标**实验环境实验步骤ARP概述 作用&#xff1a;ARP&#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff…

互联网大厂面试:大模型应用开发岗位核心技术点解析

互联网大厂面试&#xff1a;大模型应用开发岗位核心技术点解析 第一轮&#xff1a;大模型基础与上下文工程 问题 1&#xff1a;你能简单介绍 Transformer 架构的工作原理吗&#xff1f; 小C&#xff1a;嗯&#xff0c;我理解是 Transformer 主要依赖自注意力机制&#xff08;Se…

【深度学习新浪潮】有没有什么方法可以将照片变成线描稿,比如日式漫画的那种?

一、技术原理与研究进展 1. 线描生成的核心技术路径 传统方法:基于边缘检测(如Canny算子)和形态学操作,但难以处理复杂纹理和艺术风格。 深度学习方法: 端到端生成:使用U-Net架构(如ArtLine项目)直接学习照片到线描的映射,结合自注意力机制和感知损失提升细节保留能力…

NV032NV037美光固态闪存NV043NV045

NV032NV037美光固态闪存NV043NV045在数字化浪潮席卷全球的当下&#xff0c;存储技术的每一次突破都深刻影响着从个人消费到企业级应用的各个领域。美光科技作为行业领军者&#xff0c;其NV系列固态闪存产品始终以技术创新为核心驱动力。本文将聚焦NV032、NV037、NV043、NV045四…

天硕G40工业固态硬盘破解轨道存储难题

在高铁与轨道交通高速发展的今天&#xff0c;轨道检测探伤是保障列车安全运行的核心环节。据统计&#xff0c;我国铁路总里程已突破16万公里&#xff0c;日均检测数据量超10TB。加固平板一体机作为轨道探伤领域的“移动工作站”&#xff0c;需要在跨越大江南北的极端环境中实时…

基于Velero + 阿里云 OSS的Kubernetes 集群的备份与恢复

在 Kubernetes&#xff08;K8s&#xff09;中&#xff0c;备份和恢复是保障数据安全与业务连续性的关键环节&#xff0c;主要方式包括 ETCD 备份恢复 和 Velero 备份恢复&#xff0c;两者在备份粒度、恢复影响范围、存储位置等方面存在以下差异&#xff1a; 1、ETCD 备份恢复&…

解构与重构:“真人不露相,露相非真人” 的存在论新解 —— 论 “真在” 的行为表达本质

解构与重构&#xff1a;“真人不露相&#xff0c;露相非真人” 的存在论新解 —— 论 “真在” 的行为表达本质绪论&#xff1a;传统解释的突围 —— 从 “藏才” 到 “存真”“真人不露相&#xff0c;露相非真人” 这句谚语&#xff0c;自明代《西游记》以降&#xff0c;便长期…

数据结构:哈希表、排序和查找

一、哈希算法1.将数据通过哈希算法映射成一个健值&#xff0c;存取都在同一个位置&#xff0c;实现数据的高效存储和查找&#xff0c;时间复杂度由O(n)->O(1)2.哈希碰撞&#xff1a;多个数据通过哈希算法得到的键值相同二、哈希表1.构建哈希表存放0-100之间的数据2.哈希算法…

【Java基础】Java I/O模型解析:BIO、NIO、AIO的区别与联系(Netty入门必备基础)

Java I/O模型深度解析&#xff1a;BIO、NIO、AIO的区别与联系 引言 在Java的网络编程与文件操作中&#xff0c;I/O&#xff08;输入/输出&#xff09;模型是绕不开的核心话题。从早期的BIO&#xff08;Blocking I/O&#xff09;到Java 1.4引入的NIO&#xff08;Non-blocking I/…

windows PowerToys之无界鼠标:一套键鼠控制多台设备

&#x1f4bb;简介 在多设备协作的工作场景中&#xff0c;如何实现一套键鼠控制多台设备了&#xff1f;微软推出的 PowerToys 工具集中的 Mouse Without Borders&#xff08;无界鼠标&#xff09;&#xff0c;通过软件层实现跨设备的键鼠共享与数据同步功能&#xff0c;为多台…

一道比较难的sql题,筛选出重复字段的行数

select * from 导入数据表; id city_column 1 北京,上海,广州 2 上海,上海,深圳 3 北京,杭州,北京 4 上海,广州,深圳select substring_index(khmc,,,1), * from 导入数据表 truncate table 导入数据表 select count(distinct khmc) from 导入数据表; …

【K8s】整体认识K8s之与集群外部访问--service

这一篇文章主要是对service发现新的理解 为什么要使用service服务发现&#xff1f; 首先pod的IP&#xff0c;是动态的&#xff0c;当我们重启一个pod的时候&#xff0c;它会给它分配一个新的IP&#xff0c;但是如果微服务a想要去调用微服务b&#xff0c;他是需要知道微服务b所有…

k8s(自写)

kubernetes k8s是什么&#xff1f;Kubernetes是什么&#xff1f;架构是怎么样的&#xff1f;6分钟快速入门_哔哩哔哩_bilibili kubernetes是google开源神器&#xff0c;介于应用服务和服务器之间&#xff0c;能够通过策略协调和管理多个应用服务&#xff0c;只需要一个yaml文…

实现微信小程序的UniApp相机组件:拍照、录像与双指缩放

在微信小程序开发中&#xff0c;相机功能已成为许多应用的核心组成部分。本文将介绍如何使用UniApp框架实现一个功能丰富的相机组件&#xff0c;支持拍照、录像、前后摄像头切换以及双指缩放等功能。功能概述这个相机组件具备以下核心功能&#xff1a;拍照功能&#xff1a;支持…

python pyqt5开发DoIP上位机【诊断回复的函数都是怎么调用的?】

目录 文章合集 一、底层网络接收:`_receive_loop`(触发起点) 调用时机: 核心代码: 作用: 二、数据解析:`handle_received_data`(判断是否为诊断回复) 调用时机: 核心代码(诊断回复相关部分): 作用: 三、UI显示:`add_trace_entry`(展示到界面) 调用时机: 信号…

谈物质的运动与运动的物质

运动的物质是不是物质的运动&#xff0c;如果假设是&#xff08;第一假设&#xff09;&#xff0c;那末运动的物质是物质的运动&#xff0c;而运动是物质的根本属性&#xff0c;又运动的物质是物质&#xff0c;则物质的运动是物质&#xff0c;既然运动是物质的根本属性&#xf…

【MLLM】多模态理解Ovis2.5模型架构和训练流程

note 模型架构&#xff1a;延续 Ovis 系列创新的结构化嵌入对齐设计。 Ovis2.5 由三大组件构成&#xff1a;动态分辨率 ViT 高效提取视觉特征&#xff0c;Ovis 视觉词表模块实现视觉与文本嵌入的结构对齐&#xff0c;最后由强大的 Qwen3 作为语言基座&#xff0c;处理多模态嵌…

3.3单链表专题

顺序表这种在标准库已经实现好了&#xff0c;直接调用 pushback pushfront 这些o(1)表示不额外开辟空间src为value继续走&#xff0c;下一个不是value&#xff0c;src值给dst空间&#xff0c;dst&#xff0c;dst刚好等于2&#xff0c;就是新数组长度。若从前向后两个数组元素依…

linux系统学习(15.启动管理)

目录 一、运行级别 1.运行级别 2.运行级别命令 (1)runlevel (2)init 运行级别 3.永久修改启动级别&#xff08;ubantu20.04&#xff09; 二、启动过程 &#x1f539; 总结 三、启动引导程序grub配置文件 一、运行级别 1.运行级别 2.运行级别命令 (1)runlevel (2)ini…

检索优化-混合检索

混合检索&#xff08;Hybrid Search&#xff09;是一种结合了 稀疏向量&#xff08;Sparse Vectors&#xff09; 和 密集向量&#xff08;Dense Vectors&#xff09; 优势的先进搜索技术。旨在同时利用稀疏向量的关键词精确匹配能力和密集向量的语义理解能力&#xff0c;以克服…