spring5源码篇(9)——mybatis-spring整合原理

news2024/11/18 6:02:38

spring-framework 版本:v5.3.19

spring和mybatis的整合无非主要就是以下几个方面:
1、SqlSessionFactory怎么注入?
2、Mapper代理怎么注入?
3、为什么要接管mybatis事务?

文章目录

  • 一、SqlSessionFactory怎么注入
    • SqlSessionFactoryBean
  • 二、Mapper代理怎么注入
    • MapperFactoryBean
    • SqlSessionTemplate
  • 三、为什么要接管mybatis事务
    • SpringManagedTransactionFactory

一、SqlSessionFactory怎么注入

SqlSessionFactoryBean

在mybatis-spring中,定义了一个新的factoryBean——SqlSessionFactoryBean,我们将其注入到spring容器即可。
在这里插入图片描述
其实也不难猜到,这个factoryBean的getObject肯定返回了一个SqlSessionFactory,这样spring容器中就有一个SqlSessionFactory bean了。后续就跟使用原生mybatis一样,通过这个SqlSessionFactory去生成SqlSession进而生成Mapper的代理类去操作数据库。
所以getObject方法是如何生成SqlSessionFactory?来到buildSqlSessionFactory

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(
          () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

    if (hasLength(this.typeAliasesPackage)) {
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
          .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }

    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }

    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }

    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new IOException("Failed getting a databaseId", e);
      }
    }

    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

跟原生mybatis一样,构建一个Configuration对象,然后使用SqlSessionFactoryBuilder去build一个SqlSessionFactory。需要注意的是,这个过程中就算外界传入的Configuration对象有环境、数据源、事务管理器的配置(无论是xml配置还是代码)都会被忽略,取而代之的是SqlSessionFactoryBean对应的environment、dataSource、transactionFactory属性。至此spring中就相当于有了SqlSessionFactory bean,后续spring就可以用这个SqlSessionFactory bean去生成SqlSession。

二、Mapper代理怎么注入

MapperFactoryBean

跟使用原生mybatis一样,对mapper接口生成一个个的代理对象。即在整合过程中会生成一个个代理对象bean,对应流程如下。
在这里插入图片描述
1、MapperScan注解import了MapperScannerRegistrar bean。
2、MapperScannerRegistrar是一个ImportBeanDefinitionRegistrar会注册MapperScannerConfigurer bean。
3、MapperScannerConfigurer是一个BeanFactoryPostProcessor(BeanDefinitionRegistryPostProcessor),对应的增强方法会扫描出指定路径下的Mapper,并把class type设为MapperFactoryBean。
4、MapperFactoryBean是一个FactoryBean,其getObject方法跟原生mybatis一样通过SqlSession.getMapper生成一个mapper代理对象,然后将其注入spring容器。

SqlSessionTemplate

所以是直接使用SqlSession来生成mapper的代理对象吗?其实不然,实际MapperFactoryBean的getObject中的getSqlSession()返回的是一个SqlSessionTemplate
在这里插入图片描述
SqlSessionTemplate中维护了一个SqlSession的代理对象,后续有关数据库的操作都会经过这个代理对象。
在这里插入图片描述
代理SqlSession的逻辑大概就是,如果是同一个事务就获取同一个SqlSession,如果不是同一个事务就新建一个SqlSession,然后再用这个SqlSession去做事情。这么做是为了解决SqlSession线程不安全的问题,正是因为如此导致不同事务用不同的SqlSession,所以一级缓存也就失效了。

三、为什么要接管mybatis事务

SpringManagedTransactionFactory

在SqlSessionFactoryBean的buildSqlSessionFactory中,我们默认会使用SpringManagedTransactionFactory作为我们的事务管理器来接管mybatis事务。
在这里插入图片描述
这么做是为了让 MyBatis 的事务管理与 Spring 的事务管理能够协同工作,从而实现 MyBatis 与 Spring 的事务管理一致性
具体来说,SpringManagedTransactionFactory 在创建事务时,会获取 Spring 的事务管理器,并将其封装为 MyBatis 的 Transaction 对象。在事务提交或回滚时,SpringManagedTransactionFactory 会将事务的状态同步到 Spring 的事务管理器中。

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

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

相关文章

离散数学_九章:关系(4)

9.4 关系的闭包 1、闭包(closure)的定义2、不同类型的闭包1. 自反闭包(reflexive closure)2. 对称闭包(symmetric closure)3. 传递闭包(transitive closure) 3、闭包的几个定理定理1定理2定理3 - R1∪R2定理4定理5📘例题: 4、有向图中的路径5、传递闭包1…

2000-2019年30省研发资本存量(含计算过程和原始数据)

2000-2019年30省份研发资本存量(含计算过程和原始数据)/2000-2019年30个省市R&D资本存量或研发资本存量数据 1、时间:2000-2019年 2、范围:包括30个省市不含西藏 3、指标:省研发资本存量 4、参考文献&#xff…

家用 NAS 服务器搭建 | 网络 | DNS域名解析

1、前言 使用NAS,一般除了在家里通过局域网访问,还会有外网访问的需求,即在外面通过移动网络或者其他网络访问家中的NAS。 正常情况下在外面是没有办法访问家庭网络的,甚至是nas,因为nas获取的是局域网IP,…

【Python】flask

一、Flask教程 Flask是一个免费的web框架,也是一个年轻、充满活力的小型框架,开发文档齐 全,社区活跃度高,有着众多支持者。 Flask的设计目标是实现一个WSGI的微框架, 其核心代码十分简单。 Flask框架在中小型企业中的…

pwn的kali64虚拟机环境搭建记录

自己记着备用,pwn的环境和工具 虚拟机:VMware Workstation Pro Linux版本:kali64 总参考: pwn 环境搭建(wsl/vmware) pwn入门之环境搭建 目前就装这些,以后改了再更新(但愿 安装ka…

第 4 章 HBase 进阶

第 4 章 HBase 进阶 4.1 Master 架构1)Meta 表格介绍:(警告:不要去改这个表) 4.2 RegionServer 架构1)MemStore2)WAL(预写日志)3)BlockCache 4.3 写流程2&…

使用kubeadm搭建生产环境的多master节点k8s高可用集群

环境centos 7.9 目录 1.对安装 k8s 的节点进行初始化配置 2 通过 keepalivednginx 实现 k8s apiserver 节点高可用 3、kubeadm 初始化 k8s 集群 4.扩容 k8s 控制节点,把 xuegod62 加入到 k8s 集群 5、扩容 k8s 控制节点,把 xuegod64 加入到 k8s 集群…

06_Uboot顶层Makefile分析_前期所做内容

目录 U-Boot顶层Makefile分析 版本号 MAKEFLAGS变量 命令输出 静默输出 设置编译结果输出目录 代码检查 模块编译 获取主机架构和系统 设置目标架构、交叉编译器和配置文件 调用scripts/Kbuild.include 交叉编译工具变量设置 导出其他变量 U-Boot顶层Makefile分析…

Kafka架构原理(三)

三、Kafka架构原理 3.1 整体架构图 一个典型的kafka集群中包含若干个Producer,若干个Broker,若干个Consumer,以及一个zookeeper集群; kafka通过zookeeper管理集群配置,选举leader,以及在Consumer Group发…

软件多语言文案脚本自动化方案

开发高效提速系列目录 软件多语言文案脚本自动化方案 软件多语言文案脚本自动化方案 背景目标整体方案1. 创建文案资源文件2. python脚本开发3. Python脚本执行与管理4. 人员职责分配 PyCharm使用说明1. PyCharm下载2. PyCharm安装配置3. 异常情况解决 总结 博客创建时间&…

中间件漏洞(一)CVE-2013-4547(文件名逻辑漏洞)

目录 1. 了解nginx的工作原理 2. 漏洞原理及举例分析 3. 前端php源码分析 4. 注入思路 5. 漏洞复现 5.1 上传文件并抓包分析 5.2 通过访问文件执行php 注意一点 6. 漏洞修复 1. 了解nginx的工作原理 nginx是以PHP语言为主。像Apache一样,Nginx自身是不支持解…

基于黏菌算法的极限学习机(ELM)回归预测-附代码

基于黏菌算法的极限学习机(ELM)回归预测 文章目录 基于黏菌算法的极限学习机(ELM)回归预测1.极限学习机原理概述2.ELM学习算法3.回归问题数据处理4.基于黏菌算法优化的ELM5.测试结果6.参考文献7.Matlab代码 摘要:本文利用黏菌算法对极限学习机进行优化,并…

国民技术N32G430开发笔记(15)- IAP升级 树莓派串口发送数据

IAP升级 树莓派串口发送数据 1、树莓派接入usb转串口模块后,会生成/dev/ttyUSB0节点,因为树莓派内核已经编译usb_serial以及各模块的驱动。 我们直接对ttyUSB0节点编程即可。 2、协议同上一节 cmd data_lenght data0 … datax checksum 1、获取版本…

AutoDL-GPU租用平台使用(LLM 备用)

网址:AutoDL-品质GPU租用平台-租GPU就上AutoDL 1 打开网址 查看下显卡型号及价格:A100 ( 80 G 显存) 6.68/小时 、4090(24G 显存)2.68/小时 2 创建实例 1.注册登录后进入控制台(页面右上角)&#xff0…

08 KVM虚拟机配置-总体介绍

文章目录 08 KVM虚拟机配置-总体介绍8.1 概述8.2 基本格式8.3 配置流程 08 KVM虚拟机配置-总体介绍 8.1 概述 Libvirt工具采用XML格式的文件描述一个虚拟机特征,包括虚拟机名称、CPU、内存、磁盘、网卡、鼠标、键盘等信息。用户可以通过修改配置文件,对…

【应急响应】日志自动提取分析项目ELKLogkitLogonTracerAnolog等

日志自动提取-七牛Logkit&观星应急工具 1、七牛Logkit:(Windows&Linux&Mac等) https://github.com/qiniu/logkit/ 支持的数据源(各类日志,各个系统,各个应用等) File: 读取文件中的日志数据,包…

第二章 主机规划与磁盘分区

要安装好一部Linux主机并不是那么简单的事情,你必须要针对distributions的特性、服务器软件的能力、未来的升级需求、硬件扩充性需求等等来考虑,还得要知道磁盘分区、文件系统、Linux操作较频繁的目录等等,都得要有一定程度的了解才行。 2.1…

训练CV模型常用的Tips Tricks

训练CV模型常用的Tips & Tricks主要从以下9个方面进行介绍: 图像增强更好的模型学习率和scheduler优化器正则化手段标签平滑知识蒸馏伪标签错误分析 1. 图像增强 以下列出了许多增强方式,有的甚至没见过,但是也不是每一种增强方式都是…

极化码的入门与探索

文章目录 极化码的基础先验知识二进制输入离散无记忆信道模型(Binary-input Discreten Memoryless Channel, B-DMC)二进制离散输入信道的ML判决和错误率B-DMC相关参数的定义和理解 两信道极化N信道极化的解释信道极化分解的蝶形结构补充:生成矩阵的结构 极化码的基础…

Python算法设计 - 快速排序

目录 一、快速排序二、Python算法实现 一、快速排序 快速排序的概念相信大家都能理解,下面这个算法是基于同样想法的另一种算法,其中利用到了分区。如果实施正确,这是一种非常有效的算法,在预期的O(n.log n) 时间内运行&#xff…