Mybatis源码阅读系列文章:

MyBatis源码阅读(一) --- 源码阅读环境搭建

MyBatis源码阅读(二) --- 执行流程分析

MyBatis源码阅读(三) --- 配置信息的解析以及SqlSessionFactory构建过程

MyBatis源码阅读(四) --- SqlSession的创建过程

MyBatis源码阅读(五) ---Mapper接口的获取过程

MyBatis源码阅读(六) ---mapper方法具体执行流程分析

MyBatis源码阅读(七) --- 查询结果集封装流程

MyBatis源码阅读(八) --- Executor执行器

MyBatis源码阅读(九) --- 插件原理

MyBatis源码阅读(十) --- 一级缓存、二级缓存工作原理

MyBatis源码阅读(十一) --- MyBatis事务管理机制

MyBatis源码阅读(十二) --- Spring加载MyBatis过程

目录

一、概述

二、Spring加载MyBatis过程

三、Mapper接口的获取

四、总结


一、概述

通过前面几篇文章的学习,相信小伙伴对Mybatis的认识更加深刻了,对整体的流程应该算是比较清晰了。但是我们在项目中很少单独使用Mybatis,一般都是集成到Spring中,由Spring来帮我们完成以前很多繁琐的步骤,比如管理SqlSessionFactory、创建SqlSession,并且不需要手动调用getMapper方法去获取mapper接口,直接使用autoWired自动注入进来就好了。那么Spring到底是如何整合Mybatis的,我们有必要去了解一下。

二、Spring加载MyBatis过程

首先来回顾一下,没有集成Spring的时候,Mybatis是如何使用的:

public static void main(String[] args) {//1、读取配置文件String resource = "mybatis-config.xml";InputStream inputStream;SqlSession sqlSession = null;try {inputStream = Resources.getResourceAsStream(resource);//2、初始化mybatis,创建SqlSessionFactory类实例SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//3、创建Session实例sqlSession = sqlSessionFactory.openSession();//4、获取Mapper接口UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//5、执行SQL操作User user = userMapper.getById(1L);System.out.println(user);} catch (IOException e) {e.printStackTrace();} finally {//6、关闭sqlSession会话if (null != sqlSession) {sqlSession.close();}}
}

大体步骤:

  • 1、加载Mybatis的全局配置文件;
  • 2、初始化mybatis,创建SqlSessionFactory类实例;
  • 3、创建Session会话;
  • 4、获取Mapper接口;
  • 5、执行具体SQL;

其中加载全局配置文件、创建SqlSessionFactory、扫描mapper接口都是比较重要的,所以分析Spring加载MyBatis的过程无非也就是从这几方面入手。

我们来看看Spring要集成Mybatis需要做哪些配置:

<!--定义数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/user_mybatis"/><property name="username" value="root"/><property name="password" value="root"/>
</bean><!--定义sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--数据库连接池 --><property name="dataSource" ref="dataSource"/><!--配置mybatis全局配置文件: mybatis-config.xml --><property name="configLocation" value="classpath:/mybatis-config.xml"/><!--扫描entity包,使用别名,多个用;隔开 --><property name="typeAliasesPackage" value="entity"/><!--扫描sql配置文件:mapper需要的xml文件 --><property name="mapperLocations" value="classpath:/mapper/*.xml"/>
</bean><!-- 配置mapper接口路径,并注入到spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/><property name="basePackage" value="com.wsh.mybatis.mybatisdemo.mapper"/>
</bean>

我们看到,有两个主要的配置类:

  • SqlSessionFactoryBean:Spring就是利用它来创建Mybatis的SqlSessionFactory;
  • MapperScannerConfigurer:Spring使用它来扫描我们的mapper接口,并注册到IOC中;

下面分别来看看Spring是如何帮我们自动创建SqlSessionFactory的,SqlSessionFactoryBean类的继承关系图如下:

可以看到SqlSessionFactoryBean类实现了三个接口,一个是InitializingBean,另一个是FactoryBean,还有就是ApplicationListener接口。

  • InitializingBean接口:如果某个bean实现了这个接口,当bean初始化的时候,spring就会调用该接口的实现类的afterPropertiesSet方法,去实现当spring初始化该Bean的时候所需要的逻辑;
  • FactoryBean接口:如果某个bean实现了这个接口,在调用getBean的时候会返回该工厂返回的实例对象,也就是再调一次getObject方法返回工厂的实例;
  • ApplicationListener接口:如果某个bean实现了这个接口,如果注册了该监听的话,那么就可以了监听到Spring的一些事件,然后做相应的处理;

所以我们先来看看SqlSessionFactoryBean类的afterPropertiesSet()做了什么事情?

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);/*** 实现了InitializingBean接口的Bean,在Spring初始化的时候会回调afterPropertiesSet()方法*/@Overridepublic void afterPropertiesSet() throws Exception {notNull(dataSource, "Property 'dataSource' is required");notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),"Property 'configuration' and 'configLocation' can not specified with together");//具体创建SqlSessionFactory的方法this.sqlSessionFactory = buildSqlSessionFactory();}
}

从源码中可以看到,通过buildSqlSessionFactory()方法创建了一个SqlSessionFactory,继续跟踪代码:

/*** 构建一个SqlSessionFactory实例**  默认实现使用标准的MyBatis XMLConfigBuilderAPI来构建*  基于读取器的SqlSessionFactory实例。*  从1.3.0开始,它可以被直接指定为Configuration实例(不需要配置文件)。*///org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {//全局配置对象,后续很多解析XML之后的配置都会保存在configuration对象中.Configuration configuration;//XMLConfigBuilder这个类应该比较熟悉,就是Mybatis解析XML配置的核心类XMLConfigBuilder xmlConfigBuilder = null;if (this.configuration != null) {configuration = this.configuration;if (configuration.getVariables() == null) {configuration.setVariables(this.configurationProperties);} else if (this.configurationProperties != null) {configuration.getVariables().putAll(this.configurationProperties);}} else if (this.configLocation != null) {//初始化xmlConfigBuilder,通过我们配置的configLocation全局配置的位置xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);configuration = xmlConfigBuilder.getConfiguration();} else {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");}configuration = new Configuration();if (this.configurationProperties != null) {configuration.setVariables(this.configurationProperties);}}//设置对象工厂if (this.objectFactory != null) {configuration.setObjectFactory(this.objectFactory);}if (this.objectWrapperFactory != null) {configuration.setObjectWrapperFactory(this.objectWrapperFactory);}if (this.vfs != null) {configuration.setVfsImpl(this.vfs);}//处理别名配置if (hasLength(this.typeAliasesPackage)) {String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);for (String packageToScan : typeAliasPackageArray) {configuration.getTypeAliasRegistry().registerAliases(packageToScan,typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");}}}if (!isEmpty(this.typeAliases)) {for (Class<?> typeAlias : this.typeAliases) {configuration.getTypeAliasRegistry().registerAlias(typeAlias);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registered type alias: '" + typeAlias + "'");}}}//处理插件相关if (!isEmpty(this.plugins)) {for (Interceptor plugin : this.plugins) {configuration.addInterceptor(plugin);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registered plugin: '" + plugin + "'");}}}//处理类型转换器相关逻辑if (hasLength(this.typeHandlersPackage)) {String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);for (String packageToScan : typeHandlersPackageArray) {configuration.getTypeHandlerRegistry().register(packageToScan);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");}}}if (!isEmpty(this.typeHandlers)) {for (TypeHandler<?> typeHandler : this.typeHandlers) {configuration.getTypeHandlerRegistry().register(typeHandler);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registered type handler: '" + typeHandler + "'");}}}if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmlstry {configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));} catch (SQLException e) {throw new NestedIOException("Failed getting a databaseId", e);}}//处理缓存相关if (this.cache != null) {configuration.addCache(this.cache);}if (xmlConfigBuilder != null) {try {xmlConfigBuilder.parse();if (LOGGER.isDebugEnabled()) {LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");}} catch (Exception ex) {throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);} finally {ErrorContext.instance().reset();}}if (this.transactionFactory == null) {this.transactionFactory = new SpringManagedTransactionFactory();}//设置Environment环境参数configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));if (!isEmpty(this.mapperLocations)) {//mapper接口路径for (Resource mapperLocation : this.mapperLocations) {if (mapperLocation == null) {continue;}try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),configuration, mapperLocation.toString(), configuration.getSqlFragments());//执行XML解析xmlMapperBuilder.parse();} catch (Exception e) {throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);} finally {ErrorContext.instance().reset();}if (LOGGER.isDebugEnabled()) {LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");}}} else {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");}}//最后这里调用的是sqlSessionFactoryBuilder.build构建一个sqlSessionFactory,并返回return this.sqlSessionFactoryBuilder.build(configuration);}

从源码中我们可以看到buildSqlSessionFactory()方法主要做了几件事情:

  • 1、组装全局配置对象Configuration,将很多解析XML之后的配置都会保存在configuration对象中;
  • 2、使用这些配置通过SqlSessionFactoryBuilder创建SqlSessionFactory;
  • 3、最后调用了sqlSessionFactoryBuilder.build();

下面我们看一下sqlSessionFactoryBuilder.build()方法,发现此方法就是Mybatis创建sqlSessionFactory的方法,在前面的文章中有详细介绍过,这里就不过多赘述了。

//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
}

到这里,SqlSessionFactory已经创建完成了,即SqlSessionFactoryBean的初始化完成。

下面我们看一下如何获取SqlSessionFactoryBean实例,前面说过SqlSessionFactoryBean实现了FactoryBean接口,所以当我们通过getBean获取它的实例的时候实际是调用它的getObject方法,获取到的是sqlSessionFactory。

/*** 返回IOC容器的Bean*/
@Override
public SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {afterPropertiesSet();}//返回创建好的sqlSessionFactory//sqlSessionFactory就是在afterPropertiesSet()方法执行完后赋值的return this.sqlSessionFactory;
}/*** 返回IOC容器Bean的类型*/
@Override
public Class<? extends SqlSessionFactory> getObjectType() {return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}/*** 是否单例*/
@Override
public boolean isSingleton() {return true;
}

三、Mapper接口的获取

下面就需要看下Spring是如何加载我们的Mapper接口了,Spring里面主要通过MapperScannerConfigurer类来扫描我们的mapper接口,并将它们注册到IOC容器中。

  • MapperScannerConfigurer:Spring使用它来扫描我们的mapper接口,并注册到IOC中;

首先看一下MapperScannerConfigurer类的继承关系图:

重点关注一下上图红框框起来的,这是Spring扫描mapper接口的核心类:

可以看到MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,重写了postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,postProcessBeanDefinitionRegistry方法允许我们通过编码的方式,改变、新增类的定义信息。我们来看一下具体的源码:

//org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders();}//创建一个mapper扫描器ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);//设置一些属性,如SqlSessionFactory、BeanNameGenerator等scanner.setAddToConfig(this.addToConfig);scanner.setAnnotationClass(this.annotationClass);scanner.setMarkerInterface(this.markerInterface);scanner.setSqlSessionFactory(this.sqlSessionFactory);scanner.setSqlSessionTemplate(this.sqlSessionTemplate);scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);scanner.setResourceLoader(this.applicationContext);scanner.setBeanNameGenerator(this.nameGenerator);scanner.registerFilters();//扫描具体的mapper接口,并加入到IOC容器中scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

MapperScannerConfigurer已经将Mapper扫描,并加入到IOC容器中了,那我们是如何从Spring的IOC中获取mapper接口的,这时候另外一个类:MapperFactoryBean类就发挥作用了。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {private Class<T> mapperInterface;private boolean addToConfig = true;public MapperFactoryBean() {//intentionally empty }public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}/*** {@inheritDoc}*/@Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}/*** {@inheritDoc}*/@Overridepublic Class<T> getObjectType() {return this.mapperInterface;}/*** {@inheritDoc}*/@Overridepublic boolean isSingleton() {return true;}}

我们看到,MapperFactoryBean实现了FactoryBean接口,那么在调用getBean方法获取MapperFactoryBean实例的时候,实际上调用的就是getObject方法。

public T getObject() throws Exception {//具体获取流程,其实就是Mybatis自己去做了return getSqlSession().getMapper(this.mapperInterface);
}

如上可以看到,Spring内部封装了获取mapper接口的操作,所以我们不需要去手动管理SqlSession,Spring帮我们自动管理并获取mapper接口。

四、总结

本文主要总结了Spring加载MyBatis的过程,有几个关键的类:

  • SqlSessionFactoryBean:实现了InitializingBean和FactoryBean接口,主要完成的工作就是构建SqlSessionFactory对象,重点关注afterPropertiesSet()方法和getObject()方法;
  • MapperScannerConfigurer:实现了BeanDefinitionRegistryPostProcessor接口,主要完成的工作就是扫描我们配置的mapper接口,并注册到IOC中。重点关注postProcessBeanDefinitionRegistry方法;
  • MapperFactoryBean:实现了FactoryBean接口,重点关注getObject()方法,主要完成mapper接口的获取;

Spring加载MyBatis这个过程,其实就是把MyBatis的Mapper接口转换成Bean,注入到Spring容器的过程。

鉴于笔者水平有限,如果文章有什么错误或者需要补充的,希望小伙伴们指出来,希望这篇文章对大家有帮助。

MyBatis源码阅读(十二) --- Spring加载MyBatis过程相关推荐

  1. Spring源码系列(十二)Spring创建Bean的过程(二)

    1.写在前面 上篇博客主要Spring在创建Bean的时候,第一次调用的Bean的后置处理器的过程,同时笔者也打算将整个Spring创建的Bean的过程,通过这个系列,将Bean的创建过程给讲清楚,废 ...

  2. Mybatis源码阅读之二——模板方法模式与Executor

    [系列目录] Mybatis源码阅读之一--工厂模式与SqlSessionFactory 文章目录 一. 模板方法模式 二. 同步回调与匿名函数 三. Executor BaseExecutor与其子 ...

  3. Mybatis源码阅读(二):动态节点解析2.1 —— SqlSource和SqlNode

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...

  4. JVM源码阅读-Dalvik类的加载

    前言 本文主要研究Android dalvik虚拟机加载类的流程和机制.目的是了解Android中DEX文件结构,虚拟机如何从DEX文件中加载一个Java Class,以及到最终如何初始化这个类直至可 ...

  5. Mybatis源码阅读(二):动态节点解析2.2 —— SqlSourceBuilder与三种SqlSource

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...

  6. mybatis源码阅读(二):mybatis初始化上

    转载自  mybatis源码阅读(二):mybatis初始化上 1.初始化入口 //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与 ...

  7. mybatis源码阅读(三):mybatis初始化(下)mapper解析

    转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析 MyBatis 的真正强大在于它的映射语句,也是它的魔力所在.由于它的异常强大,映射器的 XML 文件就显得相对简单. ...

  8. mybatis源码阅读(四):mapper(dao)实例化

    转载自   mybatis源码阅读(四):mapper(dao)实例化 在开始分析之前,先来了解一下这个模块中的核心组件之间的关系,如图: 1.MapperRegistry&MapperPro ...

  9. 源码通透-mybatis源码分析以及整合spring过程

    源码通透-mybatis源码分析以及整合spring过程 mybatis源码分析版本:mybaits3 (3.5.0-SNAPSHOT) mybatis源码下载地址:https://github.co ...

  10. Mybatis 源码阅读环境搭建

    Mybatis源码阅读环境搭建 前言 一.下载mybatis的源码 二.编译源码 三.创建测试项目 前言     mybatis源码阅读环境搭建还是比较简单的,接下来我们讲解一下如何搭建该源码阅读环境 ...

最新文章

  1. h5打开App的方法。
  2. Log4j 日志详细用法
  3. ft232r usb uart驱动 win7_新电脑想装WIN7,技术员让装WIN10,不是忽悠你,是有原因的...
  4. oracle体系结构和组件图示,Oracle 体系结构组件
  5. LeetCode动态规划 杨辉三角
  6. Visual C++ 2008入门经典 第四章数组 字符串
  7. mysql数据表的创建-数据类型
  8. CentOS6.4 Install Nginx 1.4.4 到特定目录
  9. Servlet(2) ---2004
  10. 泡着枸杞写bug的三流程序员凭什么逆袭到一线大厂?你不服不行!
  11. flink 时间语义、水位线(Watermark)、生成水位线、水位线的传递
  12. 全国计算机等级考试二级Python(2021年9月)备考笔记 第六天
  13. 计算机控制plc应用论文,PLC自动控制系系统在变频器中的运用
  14. 同义词转换不再有效_中考英语丨初中英语句型转换大全
  15. h5后缀文件python处理
  16. 6.见过最强的后台管理系统
  17. java地图点线面_点线面类型互转
  18. 计算机中丢失swr.dll,win10电脑中模块initpki.dll加载失败提示0x80004005错误代码如何解决...
  19. 中国烟气在线监测系统行业供需现状及未来发展趋势预测报告(2022-2027年)
  20. [译]用R语言做挖掘数据《四》

热门文章

  1. python权重相似度矩阵_gensim之使用稀疏矩阵相似度,判断输入的文字意图
  2. SwiftUI 生命周期onAppear,onDisappear
  3. 430.扁平化多级双向链表
  4. 区分指针数组和数组指针
  5. linux 防火墙 80端口,Linux配置防火墙,开启80端口、3306端口
  6. Chisel:一款基于HTTP的快速稳定TCPUDP隧道工具
  7. 207.课程表(力扣leetcode) 博主可答疑该问题
  8. vue 事件调用 传参_高效开发小程序,Vue 和微信小程序的区别你该了解一下
  9. 服务器获取真实客户端 IP [ X-Forwarded-For ]
  10. 《四 spring源码》spring的事务注解@Transactional 原理分析