Mybatis整合Spring源码分析

2023-11-05

一、整合配置

POM

<!-- mybatis框架 -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.3</version>
</dependency>

<!--mybatis-spring适配器 -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.3</version>
</dependency>

需要上面两个依赖的源代码。

版本匹配:

clipboard

在spring上下文中配置SqlSessionFactoryBean,并将一些配置信息都设置到SqlSessionFactoryBean中:

xml

    <!-- 配置数据源 -->
    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!--驱动类名 -->
        <property name="driverClass" value="${jdbc.driver}" />
        <!-- url -->
        <property name="jdbcUrl" value="${jdbc.url}" />
        <!-- 用户名 -->
        <property name="user" value="${jdbc.uid}" />
        <!-- 密码 -->
        <property name="password" value="${jdbc.pwd}" />
    </bean>   	

		<!-- 会话工厂bean sqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 数据源 -->
        <property name="dataSource" ref="xxxxx"></property>
        <!-- 别名 -->
        <property name="typeAliasesPackage" value="xxxxx"></property>
        <!-- sql映射文件路径 -->
        <property name="mapperLocations" value="xxxxx"></property>
    </bean>

javaConfig

	//<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	@Bean    
	public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException {
		SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
		factoryBean.setDataSource(dataSource());
		// 设置MyBatis配置文件路径
		factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
		// 设置SQL映射文件路径
		factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
    // 设置别名
		factoryBean.setTypeAliases(User.class);

		return factoryBean;
	}


		//<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
        return dataSource;
    }

二、源码分析

1.构建SqlSessionFactory

image-20211216173215281

SqlSessionFactoryBean实现InitializingBean,会在初始化Bean时调用afterPropertiesSet,其中的buildSqlSessionFactory就等同于SqlSessionFactoryBuilder().build()方法,用于构建SqlSessionFactory

  @Override
  public void afterPropertiesSet() throws Exception {
    ...
    //通过sqlSessionFactoryBuilder来构建sqlSessionFactory
    //等同于new SqlSessionFactoryBuilder().build();  
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

buildSqlSessionFactory,将SqlSessionFactoryBean中设置的属性设置到Configuration对象中,Configuration对象用于保存mybatis的所有的配置信息。

  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    ...
      			//解析mapper映射文件
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
    ...
    //通过建造者模式构建DefaultSqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }      

2.mybatis-spring事务集成

如果在Mybatis中配置了事务,那么Mybatis中的事务要和Spring中的事务集成在一起。

Spring开启事务时会获取Connection,并最终存到TransactionSynchronizationManager中。Mybatis想要集成Spring的事务,就需要拿到Spring开启事务时获取的Connection,具体逻辑源码:

1.当buildSqlSessionFactory时,会设置environment,当transactionFactory为null的时候会创建一个新的mybatis-spring适配的事务工厂类SpringManagedTransactionFactory

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

2.当调用SpringManagedTransactionFactory的newTransaction时,会创建一个SpringManagedTransaction

public class SpringManagedTransactionFactory implements TransactionFactory {

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

3.当mybatis获取connection时就会调用SpringManagedTransaction的getConnection,最终获取到的connection就是TransactionSynchronizationManager中的Connection

public class SpringManagedTransaction implements Transaction {
  ...
  @Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }
  
  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    ....
  }    

DataSourceUtils

public abstract class DataSourceUtils {
    ...

    public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        }...
    }
  
  
    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        ...
        ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
        ....
    }  

3.Spring管理Mapper代理对象

Mapper接口底层实现

Mapper接口底层使用JDK动态代理来实现。通过sqlSession.getMapper方法就可以获取对应的Mapper接口,由此查看源码:

  //DefaultSqlSession#getMapper
	@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

	//Configuration#getMapper
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

	//MapperRegistry#getMapper
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    ...
    try {
      //通过MapperProxyFactory来创建实例
      return mapperProxyFactory.newInstance(sqlSession);
    } ...
  }

	//MapperProxyFactory#newInstance
  public T newInstance(SqlSession sqlSession) {
    //创建代理对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    //创建Mapper代理对象返回
    return newInstance(mapperProxy);
  }

	//MapperProxyFactory#newInstance
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

JDK动态代理实现类MapperProxy,当我们调用Mapper接口中的某一方法,就会来到MapperProxy的invoke方法。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ...
    return mapperMethod.execute(sqlSession, args);
  }

MapperMethod#execute,底层最终还是通过sqlSession的方式来执行。

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //sql命令的类型
    switch (command.getType()) {
      //insert操作
      case INSERT: {
        ...
      }
      //update操作
      case UPDATE: {
        ...
      }
      //delete操作
      case DELETE: {
        ...
      }
      //select操作
      case SELECT:
        ...
      case FLUSH:
        ...
      default:
        ...
    return result;
  }

*Spring管理Mapper代理对象

Mapper代理对象创建后,还需要把Mapper代理对象作为一个bean注入到Spring容器中,使得能够像普通bean一样被@Autowire自动注入。

1.通过@MapperScan(basePackages = {“com.xxx.mapper”})注解指定需要扫描的mapper接口包路径。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

2.通过@Import导入MapperScannerRegistrar,实现了ImportBeanDefinitionRegistrar。它会在beanDefinition扫描的时候调用registerBeanDefinitions()方法往Spring容器中添加beanDefinition对象到 beanDefinitionMap中。

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    ...

    //为容器中注册了MapperScannerConfigurer的接口
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

3.注册MapperScannerConfigurer,实现了BeanDefinitionRegistryPostProcessor,重写其postProcessBeanDefinitionRegistry方法,并会在IOC容器的refresh—>invokeBeanFactoryPostProcessors方法中调用。

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
	...
    
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    ...

    //创建一个ClassPathMapperScanner包扫描器对象
    //mybaits-spring适配器中的,继承了spring中的ClassPathBeanDefinitionScanner
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    ...
    //真正去扫描@MapperScan指定的路径下的bean定义信息
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }    

4.创建ClassPathMapperScanner对象,是mybaits-spring适配器中自定义的一个扫描类,继承了spring中的ClassPathBeanDefinitionScanner。通过doScan进行扫描,将mapper接口都扫描成beanDefinition。

  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用父类ClassPathBeanDefinitionScanner 来进行扫描
    //会通过isCandidateComponent判断是否符合条件,该方法被重写
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    ...
      //进行处理,偷天换日操作
      processBeanDefinitions(beanDefinitions);
		...	
  }

	//重写,只有是接口时满足扫描条件
	//spring中默认是不是接口满足条件
  @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

5.重点方法,通过processBeanDefinitions对所有mapper接口的beanDefinition循环进行处理,将BeanClass偷换成MapperFactoryBean。

因为接口没有办法进行实例化,所以创建成FactoryBean,在调用getObject时就会给mapper会创建动态代理。

但是创建动态代理需要被代理的mapper接口,MapperFactoryBean中的属性mapperInterface就用来指定mapper接口,通过构造函数的方式进行传入。

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;     
    for (BeanDefinitionHolder holder : beanDefinitions) {
    ...

      // 设置ConstructorArgumentValues 会通过构造器初始化对象
      // 传入String类型的名字,Spring会根据名字获取到class
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      // 将BeanClass设置成MapperFactoryBean,实现了FactoryBean
    	// 其中有属性mapperInterface,指向要创建动态代理的接口
    	// MapperFactoryBean通过构造方法设置了mapperInterface
      definition.setBeanClass(this.mapperFactoryBeanClass);

      ...
        //根据类型自动注入,会将容器中已经存在的SqlSessionFactory注入进来
        //MapperFactoryBean继承了SqlSessionDaoSupport,其中有setSqlSessionFactory
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      ...
    }
  }

6.当我们调用到mapper接口时(getBean),就会调用FactoryBean的getObject方法,创建动态代理。

  @Override
  public T getObject() throws Exception {
    //进入sqlSession.getMapper逻辑
    return getSqlSession().getMapper(this.mapperInterface);
  }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Mybatis整合Spring源码分析 的相关文章

随机推荐

  • Flask框架之视图高级技巧

    文章目录 4 1 app route与app url rule简介 4 1 1 app route的使用 4 1 2 add url rule的使用 4 2 Flask类视图 4 2 1 标准类视图 4 2 2 基于方法的类视图 4 3 F
  • win10c语言文件不运行,教你解决win10系统无法打开EXE文件的问题

    win10电脑上的程序一般都是exe格式的 鼠标双击一下即可运行 最近 有Win10系统的用户抱怨说竟然打不开Exe的文件 双击好多次了 甚至关机重启还是一样打不开Exe的文件 真是让人感到疑惑 其实 遇到此故障问题不必慌张 大家可以尝试按
  • 拜耳再投4亿元提升在华处方药产能;阿斯利康进博会公布新冠疫苗最新进展

    进博会看点 拜耳围绕 共享健康 消除饥饿 的全新企业愿景 携一系列亮点展品和精彩活动亮相进博会 进博会上 拜耳与北京经济技术开发区签署合作意向书 拜耳今年在公共卫生防疫专区设置独立的展台 并呈现覆盖预防 诊断 缓解及治疗于一体的医疗解决方案
  • vue3+ts 使用qrcode(解决了找不到qrcode类型声明文件问题)

    1 安装 全局安装 npm install g qrcode 安装类型声明 npm i save dev types qrcode 2 在所需处引用 import QRcode from qrcode 3 配合canvas 生成二维码
  • datax 数据写入oracle报错缺失表达式_实战再次升级:流批一体处理百亿级别数据...

    需求背景 该篇内容基于之前写过的一篇 lt gt 上一篇文章其实主要重点是结合logstash的实际应用 近期业务方提出了新的需求 增加了些业务逻辑 同时数据量也成倍增加 要求每日产出指标结果 这里再回顾下上篇的数据情况和技术方案同时对比下
  • word 文档标题样式相同、行距效果不同的解决办法

    先看下图示例 两个标题均采用了标题1的样式 段前 段后0 5行 单倍行距 但展现出的效果有很大差异 修改办法 将鼠标放置在需要修改的标题页 菜单栏点击 布局 gt 点击 页面设置 右下角扩展按钮 gt 点击 文档网络 调整成和第二章一样的行
  • ruoyi 修改主题颜色

    ruoyi 修改主题颜色 找到样式文件 src main resources static css skins css 1 修改顶部右侧导航栏背景颜色 skin yellow navbar background color 4EA3E4 2
  • 通用语言大模型都是人、机、环境系统智能

    通用语言大模型可以被看作是人 机 环境系统智能发展的早期阶段之一 这些模型使用深度学习和自然语言处理等技术 通过对大量数据的学习和训练 能够生成具有语义理解和生成能力的文本回答 在这个系统中 人类作为用户与AI模型进行交互 提供问题或指令
  • Linux异常(中断)处理体系结构

    前言 可以调到总结处先看明白这篇文章要说明的内容 再回到开头看 1 异常 异常 就是可以打断CPU正常运行流程的一些事情 比如 外部中断 未定义的指令 企图修改只读的数据 执行SWI指令 Software Interrupt Instruc
  • 【Unity】鼠标划入划出检测

    Unity 鼠标划入划出检测 在Unity2D中检测鼠标划入划出有专门的内置函数 在使用时要应用相关函数库 继承相关类才行 相关脚本如下 using System Collections using System Collections G
  • bootstrap的portlet组件框框的应用 (点击可伸缩)

    上面用是bootstrap做成的一个经典网站一部分 看右半部分 先分析布局 是这样的 一个大的div 分上下两部分 以一条灰色线区隔开来 上面是说明性文字 和右侧的特殊按钮 如伸缩 刷新 和移除按钮等 下面则是真正的内容部分 把网站的htm
  • pycharm界面怎么自动换行

    pycharm界面怎么自动换行 第一步我们单击桌面上的pycharm图标 第二步找到文件下的设置属性 单击按下就成功开启编辑窗口使用自动换行 还有一个在代码显示界面旁边点击右键 代码框就实现了代码自动换行的
  • 小程序WXML语法

    一 WXML模块语法 数据绑定 1 数据绑定的基本原则 1 在data中定义数据 2 在WXML中使用数据 2 在data中定义页面的数据 在页面对应的 js文件中 把数据定义到data对象中即可 Page data 字符串类型的数据 in
  • Android The number of method references in a .dex file cannot exceed 64K.

    编译项目时报错 The number of method references in a dex file cannot exceed 64K 查阅得知 在Android系统中 一个App的所有代码都在一个Dex文件里面 Dex是一个类似J
  • 如何成为嵌入式软件工程师_成为高效软件工程师的三个关键

    如何成为嵌入式软件工程师 In this one on one episode of the Versioning Show David and Tim look at what it means to be a productive so
  • 微众银行区块链2021年度回顾

    2021年 数字经济不断深化 并有望担当起更为重要的历史使命 同时 十四五 规划纲要明确区块链作为七大数字经济重点产业之一 为数字产业化提供有力技术支撑 助推数字经济转型升级 这一年 微众银行继续坚守联盟链技术路线 以安全可控的开源技术推动
  • odoo(搭建部署资源、教程)

    本地部署odoo16 odoo https www odoo com documentation 14 0 zh CN administration install install html mac os https alanhou org
  • Ubuntu18.04没有WiFi怎么解决(图文详解)

    博主一个月前 2021 6 1 在安装Ubuntu后出现了没有WiFi的问题 参考了很多教程 才成功解决这个问题 中间做了很多无用功 所以在此总结几位大佬的方法 希望对大家有帮助 少走弯路 1 问题描述 登录Ubuntu系统后 在WiFi设
  • mlxtend库:打造自己的机器学习工具箱

    mlxtend库 打造自己的机器学习工具箱 你是否曾经为缺少一个方便 易用的机器学习工具而感到烦恼 mlxtend库或许可以满足你的需求 mlxtend是一个基于Python的开源项目 它提供了许多用于数据预处理 特征选择 分类 聚类等机器
  • Mybatis整合Spring源码分析

    一 整合配置 POM