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

Spring和mybatis整合后事务拦截器TransactionInterceptor开启提交事务流程

目录

  • 一、说明
  • 二、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会提交事务。如果开启了,使用事务拦截器统一提交事务。
在这里插入图片描述
分析一下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,执行提交操作。

在这里插入图片描述
执行到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.dtcms.com/a/358998.html

相关文章:

  • 【Java实战⑧】Java常用类实战:解锁String、Object与包装类的奥秘
  • STL中的容器,迭代器
  • 规律作息 + 养成好的习惯 + 考研倒计时 111 天 + 线面积分入门 1 下半部分
  • 【路由器】TP Link 路由器为何无法进入管理后台
  • HarmonyOS AppStorage:跨组件状态管理的高效解决方案
  • 2025年06月 Scratch 图形化(二级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • 大模型训练中的 logits 是什么
  • npm基础
  • SNMPv3开发--snmpd.conf
  • Vue加载速度优化,verder.js和element.js加载速度慢解决方法
  • VGG改进(6):基于PyTorch的VGG16-SE网络实战
  • 项目管理方法适用场景对比
  • Linux kernel arm64 启动流程
  • ubuntu 安装conda, ubuntu24安装miniConda
  • python制作一个股票盯盘系统
  • 三重积分从入门到入土
  • 微风PLC编程软件下载(C4G02_Develop)
  • GESP5级2024年03月真题解析
  • Python实现全角数字转半角数字的完整教程
  • 一站式可视化运维:解锁时序数据库 TDengine 的正确打开方式
  • 数值分析——算法的稳定性
  • 鸿蒙服务端开发资料汇总
  • 中级统计师-统计实务-第三章 国民经济核算
  • 从支付工具到收益资产:稳定币在 Berachain 上的二次进化
  • 数位 dp
  • 函数(1)
  • React useState基本使用
  • 鸿蒙ArkTS 核心篇-13-if分支语句
  • 玄机靶场 | 第五届红明谷-异常行为溯源
  • Fortran二维数组去重(unique)算法实战