spring 事物源码阅读之事务与持久化操作的结合

news2024/11/18 16:36:09

前面文章说到在开启事务后,会将数据库连接存放在当前线程的ConnectionHolder。那么后续的数据库持久化操作是怎么感知的呢。这里就要说到一个重要的类TransactionSynchronizationManager。

TransactionSynchronizationManager

TransactionSynchronizationManager是一个桥梁用来连接事务和中间持久化操作逻辑。主要是共享数据库connection及事务的判断。

来看下spring官方文档对TransactionSynchronizationManager解释:

TransactionSynchronizationManager是一个中央委托机制,用于管理每个线程的资源和事务同步。它被用于资源管理代码,而不是典型的应用程序代码。

资源管理代码应该通过getResource方法来检查与线程绑定的资源,例如JDBC连接或Hibernate会话。通常情况下,该代码不应该将资源绑定到线程,因为这是事务管理器的责任。另一个选项是在第一次使用时延迟绑定,如果事务同步处于活动状态,则可以执行跨多个资源的事务。

事务同步必须由事务管理器通过initSynchronization()和clearSynchronization()来激活和停用。AbstractPlatformTransactionManager自动支持此功能,并且所有标准的Spring事务管理器(如JtaTransactionManager和DataSourceTransactionManager)也支持该功能。

当该管理器处于活动状态时,资源管理代码应仅在此注册同步,可以通过isSynchronizationActive()进行检查;否则,应立即执行资源清理操作。如果事务同步未处于活动状态,则表示当前没有事务,或者事务管理器不支持事务同步。

同步机制例如用于在JTA事务中始终返回相同的资源,例如给定DataSource的JDBC连接或给定SessionFactory的Hibernate会话。

下面看下该类的主要结构

主要属性

//事务资源
ThreadLocal<Map<Object, Object>> resources;
ThreadLocal<Set<TransactionSynchronization>> synchronizations;
//当前事务名称
final ThreadLocal<String> currentTransactionName;
//当前事务是否只读
ThreadLocal<Boolean> currentTransactionReadOnly;
//当前事务隔离级别
ThreadLocal<Integer> currentTransactionIsolationLevel;
//当前是否有事务
ThreadLocal<Boolean> actualTransactionActive;

这里看到所有都是ThreadLocal类型变量。同样提供了静态方法对以上属性进行设置和获取

主要方法:
在这里插入图片描述

属性的设置

这些相关属性都是事务相关,所以要从开启事务地方开始看

1AbstractPlatformTransactionManager#startTransaction

private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
      boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

   boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
   DefaultTransactionStatus status = newTransactionStatus(
         definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
   doBegin(transaction, definition);
   prepareSynchronization(status, definition);
   return status;
}

这里有两步

第一步doBegin

DataSourceTransactionManager#doBegin

protected void doBegin(Object transaction, TransactionDefinition definition) {
   DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
   Connection con = null;

   try {
      if (!txObject.hasConnectionHolder() ||
            txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
         Connection newCon = obtainDataSource().getConnection();
         if (logger.isDebugEnabled()) {
            logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
         }
         txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
      }

      txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
      con = txObject.getConnectionHolder().getConnection();

      Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
      txObject.setPreviousIsolationLevel(previousIsolationLevel);
      txObject.setReadOnly(definition.isReadOnly());
      if (con.getAutoCommit()) {
         txObject.setMustRestoreAutoCommit(true);
         if (logger.isDebugEnabled()) {
            logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
         }
         con.setAutoCommit(false);
      }

      prepareTransactionalConnection(con, definition);
      txObject.getConnectionHolder().setTransactionActive(true);

      int timeout = determineTimeout(definition);
      if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
         txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
      }

      // Bind the connection holder to the thread.
      if (txObject.isNewConnectionHolder()) {
         TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
      }
   }

}

这里看到最后一步调用bindResource将ConnectionHolder存放到了resources里,这里的key值是datasource。

第二步prepareSynchronization

protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
   if (status.isNewSynchronization()) {
      TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
      TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
            definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
                  definition.getIsolationLevel() : null);
      TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
      TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
      TransactionSynchronizationManager.initSynchronization();
   }
}

这里设置了其它属性的值,其中入参definition是@Transactionnal注解配置信息

JdbcTemplate

首先jdbcTemplate初始化时候需要注入一个datasource

@Bean
public JdbcTemplate jdbcTemplate() {
    return new JdbcTemplate(dataSource());
}

JdbcTemplate#execute方法

private <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources)
      throws DataAccessException {
  //获取连接
   Connection con = DataSourceUtils.getConnection(obtainDataSource());
   PreparedStatement ps = null;
   try {
      ps = psc.createPreparedStatement(con);
      applyStatementSettings(ps);
      T result = action.doInPreparedStatement(ps);
      handleWarnings(ps);
      return result;
   }
   catch (SQLException ex) {
      //...
   }
   finally {
      if (closeResources) {//关闭连接
         DataSourceUtils.releaseConnection(con, getDataSource());
      }
   }
}

获取连接getConnection方法最后会调用doGetConnection方法获取连接

DataSourceUtils#doGetConnection

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
  
   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);

   if (TransactionSynchronizationManager.isSynchronizationActive()) {
      //...
   }

   return con;
}

这里看到获取连接首先会从ConnectionHolder里取获取连接,如果获取不到在从datasource里取一个连接。所以如果开启了事务就是使用事务开启时存放到ConnectionHolder里的Connection。

MyBatis

同样的mybatis在获取sqlsession的时候也是先会从ConnectionHolder里尝试获取。

SqlSessionUtils#getSqlSession

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    return session;
  }

  LOGGER.debug(() -> "Creating a new SqlSession");
  session = sessionFactory.openSession(executorType);

  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

这里的SqlSessionHolder是继承自ResourceHolderSupport,包装了一下把mybatis的session,执行类型存放进去,ConnectionHolder也是继承自ResourceHolderSupport。这里getResource的key是sessionFactory,上面分析事务开启设置的resource是connection。

sessionHolder方法就是从holder里取session。那么session是什么时候放到holder呢?

这要看registerSessionHolder方法

SqlSessionUtils#registerSessionHolder

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
  SqlSessionHolder holder;
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
    Environment environment = sessionFactory.getConfiguration().getEnvironment();

    if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
      LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
      //包装session到holder
      holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
      TransactionSynchronizationManager.bindResource(sessionFactory, holder);
      //注册SqlSessionSynchronization
      TransactionSynchronizationManager
          .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
      holder.setSynchronizedWithTransaction(true);
      holder.requested();
    } else {
      if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
        LOGGER.debug(() -> "SqlSession [" + session
            + "] was not registered for synchronization because DataSource is not transactional");
      } else {
        throw new TransientDataAccessResourceException(
            "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
      }
    }
  } else {
    LOGGER.debug(() -> "SqlSession [" + session
        + "] was not registered for synchronization because synchronization is not active");
  }

}

第一次取肯定取不到session,因为事务开启的时候key值是datasource,sqlsession是mybatis特有的这里以sessionFacotry作为key进行存储,取不到会调用registerSessionHolder方法进行SessionHolder注册,如有下一个mapper在获取session的时候就可以正常取到了。

TransactionSynchronizationManager的resources是一个map结构存储,key值不同不会覆盖原来事务设置的connectionholder。

这里设置resources的同时还注册了一个SqlSessionSynchronization,这个在下面解除绑定的时候会回调完成解除。

解除绑定

解除绑定通过回调Synchronization的beforeCompletion来完成。这里SqlSessionSynchronization是SqlSessionUtils的一个内部类

SqlSessionSynchronization#beforeCompletion

public void beforeCompletion() {
  if (!this.holder.isOpen()) {
    LOGGER
        .debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
    TransactionSynchronizationManager.unbindResource(sessionFactory);
    this.holderActive = false;
    LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
    this.holder.getSqlSession().close();
  }
}

这里会将sessionFactory从TransactionSynchronizationManager中移除,在事务提交的时候回调。

在事务TransactinoManager处理提交的方法processCommit中有一步triggerBeforeCompletion(status);这里就会调到

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
      boolean beforeCompletionInvoked = false;

    boolean unexpectedRollback = false;
    prepareForCommit(status);
    triggerBeforeCommit(status);
    //这一步解除绑定
    triggerBeforeCompletion(status);
    beforeCompletionInvoked = true;
		//...
}

最后会调用

TransactionSynchronizationUtils#triggerBeforeCompletion

public static void triggerBeforeCompletion() {
   for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
         synchronization.beforeCompletion();
   }
}

这里就是拿出注册的Synchronizations进行依次调用,回看下前面在绑定的时候注册了一个new SqlSessionSynchronization(holder, sessionFactory)。最后调用其beforeCompletion完成解除绑定。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IDEA2021创建Web项目配置Tomcat

1.新建一个普通的项目。 2.右键新建的项目&#xff0c;选择添加框架支持 3.勾选web application 4.在WEB-INF里创建lib和classes文件夹 5.file-project structure-modules-paths&#xff0c;选择use module compile output path&#xff0c;将output path和test output path的路…

Springboot知识点必知必会(一)

mvc设计模式 MVC设计模式是Model-View-Controller的缩写&#xff0c;它是一种用于设计用户界面的软件设计模式。Spring MVC是Spring框架的一个模块&#xff0c;它提供了一种基于Java的方式来实现MVC设计模式。 以下是Spring MVC中MVC设计模式的组成部分和工作原理&#xff1a; …

在Linux中通过docker安装appnode面板

先在Linux中安装docker&#xff0c;然后在docker中安装appnode面板&#xff0c;并进行docker网络端口映射。 安装docker 第一步&#xff0c;卸载旧版本docker。 若系统中已安装旧版本docker&#xff0c;则需要卸载旧版本docker以及与旧版本docker相关的依赖项。 命令&#…

计算机竞赛 : 题目:基于深度学习的水果识别 设计 开题 技术

1 前言 Hi&#xff0c;大家好&#xff0c;这里是丹成学长&#xff0c;今天做一个 基于深度学习的水果识别demo 这是一个较为新颖的竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-senior/pos…

访问Apache Tomcat的虚拟主机管理页面

介绍 通过Tomcat Host Manager应用可以创建、删除、管理Tomcat内的虚拟主机&#xff08;virtual hosts&#xff09;。该应用是Tomcat安装的一部分&#xff0c;默认在<Tomcat安装目录>/webapps/host-manager&#xff1a; 配置用户名、密码、角色 要访问Host Manager应…

MySQL-1(12000字详解)

一&#xff1a;数据库的引入 数据库在我们以后工作中是一个非常常用的知识&#xff0c;数据库用来存储数据&#xff0c;但是有些同学可能就会疑惑了&#xff0c;存储数据用文件就可以了&#xff0c;为什么还要弄个数据库呢&#xff1f; 文件保存数据有以下几个缺点&#xff1…

Mainflux IoT:Go语言轻量级开源物联网平台,支持HTTP、MQTT、WebSocket、CoAP协议

Mainflux是一个由法国的创业公司开发并维护的安全、可扩展的开源物联网平台&#xff0c;使用 Go语言开发、采用微服务的框架。Mainflux支持多种接入设备&#xff0c;包括设备、用户、APP&#xff1b;支持多种协议&#xff0c;包括HTTP、MQTT、WebSocket、CoAP&#xff0c;并支持…

vue实现echarts中 9种 折线图图例

let datas [{ DivideScore: 7, UserScore: 7.2, Name: 目标制定 },{ DivideScore: 7, UserScore: 7, Name: 具体性 },{ DivideScore: 7, UserScore: 7.5, Name: 可衡量性 },{ DivideScore: 7, UserScore: 7, Name: 可实现性 },{ DivideScore: 7, UserScore: 7, Name: 时间限定…

【软件测试】一份合格的软件测试简历长什么样?

你可以写一篇出众的软件测试简历并且这篇测试用例能够为你带来面试电话么&#xff1f;如果没有&#xff0c;请继续阅读。我敢肯定&#xff0c;读完这篇文章&#xff0c;你将能够写出一个完美的杀手级别的软件测试和质量保证的简历&#xff0c;这将为你带来面试电话。 你的简历是…

图片调色盘

图片预览 配置安装 Color-Thief 安装包使用文档 yarn add colorthief -S // npm install colorthief --save代码 <template><div class"img-thief"><div class"container"><div class"thief-item" v-for"(item, in…

邮件误操作删除,如何找回?这里有救援方法!

用户遇到的问题 ​“邮件不慎删除&#xff0c;要如何找回呢&#xff1f;今天我在查阅邮件的过程中&#xff0c;我注意到一封带附件的邮件&#xff0c;原本是打算将其另存到其他位置&#xff0c;却无意间点击了删除&#xff0c;这之后就再也无法找回了。现在我应该怎么办&am…

java生成带logo的二维码和下方带内容的条形码

一、导入zxing包 <!-- zxing --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.2</version></dependency><dependency><groupId>com.google.zxing</group…

【数据结构】归并排序和计数排序(排序的总结)

目录 一&#xff0c;归并排序的递归 二&#xff0c;归并排序的非递归 三&#xff0c;计数排序 四&#xff0c;排序算法的综合分析 一&#xff0c;归并排序的递归 基本思想&#xff1a; 归并采用的是分治思想&#xff0c;是分治法的一个经典的运用。该算法先将原数据进行拆…

what?es数据偏移了8小时...

今天搞监控大屏的时候&#xff0c;测试突然提出一个问题说&#xff0c;查一段时间的数据&#xff0c;时间曲线返回的日期有时候会比查询时间多&#xff0c;翻看代码后&#xff0c;初步定位为es的时区问题&#xff0c;后来将时间曲线的直方图聚合增加时区后&#xff0c;返回数据…

android studio 、JDK环境变量配置

1、adb.exe环境变量配置&#xff1a; 打开控制面板 >系统和安全>系统>高级系统设置 在系统变量中新建ANDROID_HOME变量&#xff0c;赋值路径&#xff1a;D:\install\androidSDK 在系统变量path中添加&#xff1a;%ANDROID_HOME%\platform-tools 校验是…

14:00面试,14:06就出来了,这面试问的过于变态了。。。

前言 刚从小厂出来&#xff0c;没想到在另一家公司我又寄了。 在这家公司上班&#xff0c;每天都要加班&#xff0c;但看在钱给的比较多的份上&#xff0c;也就不太计较了。但万万没想到十月一纸通知&#xff0c;所有人不准加班了&#xff0c;不仅加班费没有了&#xff0c;薪资…

什么是事件对象(event object)?如何使用它获取事件信息?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

分类预测 | MATLAB实现KOA-CNN-LSTM开普勒算法优化卷积长短期记忆神经网络数据分类预测

分类预测 | MATLAB实现KOA-CNN-LSTM开普勒算法优化卷积长短期记忆神经网络数据分类预测 目录 分类预测 | MATLAB实现KOA-CNN-LSTM开普勒算法优化卷积长短期记忆神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现KOA-CNN-LSTM开普勒算法优化…

NFTScan | 10.02~10.08 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2023.10.02~ 2023.10.08 NFT Hot News 01/ 9 月 OpenSea 交易额为 7300 万美元&#xff0c;创两年新低 10 月 2 日&#xff0c;数据显示 9 月 NFT 平台 OpenSea 的交易总额为 73,141,407…

Pyside6 QPushButton

Pyside6 QPushButton QPushButton使用QPushButton继承关系QPushButton的函数(Function)和信号(Signal)QPushButton信号 QPushButton例程界面设计clicked信号测试pressed信号测试released信号测试toggled信号测试按键长按测试按键长按间隔测试完整程序界面程序主程序 按键或命令…