该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址Mybatis-Spring 源码分析 GitHub 地址Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的初始化

在MyBatis初始化过程中,大致会有以下几个步骤:

  1. 创建Configuration全局配置对象,会往TypeAliasRegistry别名注册中心添加Mybatis需要用到的相关类,并设置默认的语言驱动类为XMLLanguageDriver

  2. 加载mybatis-config.xml配置文件、Mapper接口中的注解信息和XML映射文件,解析后的配置信息会形成相应的对象并保存到Configuration全局配置对象中

  3. 构建DefaultSqlSessionFactory对象,通过它可以创建DefaultSqlSession对象,MyBatis中SqlSession的默认实现类

因为整个初始化过程涉及到的代码比较多,所以拆分成了四个模块依次对MyBatis的初始化进行分析:

  • 《MyBatis初始化(一)之加载mybatis-config.xml》
  • 《MyBatis初始化(二)之加载Mapper接口与XML映射文件》
  • 《MyBatis初始化(三)之SQL初始化(上)》
  • 《MyBatis初始化(四)之SQL初始化(下)》

由于在MyBatis的初始化过程中去解析Mapper接口与XML映射文件涉及到的篇幅比较多,XML映射文件的解析过程也比较复杂,所以才分成了后面三个模块,逐步分析,这样便于理解

初始化(二)之加载Mapper接口与映射文件

在上一个模块已经分析了是如何解析mybatis-config.xml配置文件的,在最后如何解析<mapper />标签的还没有进行分析,这个过程稍微复杂一点,因为需要解析Mapper接口以及它的XML映射文件,让我们一起来看看这个解析过程

解析XML映射文件生成的对象主要如下图所示:

主要包路径:org.apache.ibatis.builder、org.apache.ibatis.mapping

主要涉及到的类:

  • org.apache.ibatis.builder.xml.XMLConfigBuilder:根据配置文件进行解析,开始Mapper接口与XML映射文件的初始化,生成Configuration全局配置对象
  • org.apache.ibatis.binding.MapperRegistry:Mapper接口注册中心,将Mapper接口与其动态代理对象工厂进行保存,这里我们解析到的Mapper接口需要往其进行注册
  • org.apache.ibatis.builder.annotation.MapperAnnotationBuilder:解析Mapper接口,主要是解析接口上面注解,其中加载XML映射文件内部会调用XMLMapperBuilder类进行解析
  • org.apache.ibatis.builder.xml.XMLMapperBuilder:解析XML映射文件
  • org.apache.ibatis.builder.xml.XMLStatementBuilder:解析XML映射文件中的Statement配置(<select /> <update /> <delete /> <insert />标签)
  • org.apache.ibatis.builder.MapperBuilderAssistant:Mapper构造器小助手,用于创建ResultMapping、ResultMap和MappedStatement对象
  • org.apache.ibatis.mapping.ResultMapping:保存<resultMap />标签的子标签相关信息,也就是 Java Type 与 Jdbc Type 的映射信息
  • org.apache.ibatis.mapping.ResultMap:保存了<resultMap />标签的配置信息以及子标签的所有信息
  • org.apache.ibatis.mapping.MappedStatement:保存了解析<select /> <update /> <delete /> <insert />标签内的SQL语句所生成的所有信息

解析入口

我们回顾上一个模块,在org.apache.ibatis.builder.xml.XMLConfigBuilder中会解析mybatis-config.xml配置文件中的<mapper />标签,调用其parse()->parseConfiguration(XNode root)->mapperElement(XNode parent)方法,那么我们来看看这个方法,代码如下:

private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// <0> 遍历子节点
for (XNode child : parent.getChildren()) {
// <1> 如果是 package 标签,则扫描该包
if ("package".equals(child.getName())) {
// 获得包名
String mapperPackage = child.getStringAttribute("name");
// 添加到 configuration 中
configuration.addMappers(mapperPackage);
} else { // 如果是 mapper 标签
// 获得 resource、url、class 属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// <2> 使用相对于类路径的资源引用
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 获得 resource 的 InputStream 对象
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建 XMLMapperBuilder 对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 执行解析
mapperParser.parse();
// <3> 使用完全限定资源定位符(URL)
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
// 获得 url 的 InputStream 对象
InputStream inputStream = Resources.getUrlAsStream(url);
// 创建 XMLMapperBuilder 对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,configuration.getSqlFragments());
// 执行解析
mapperParser.parse();
// <4> 使用映射器接口实现类的完全限定类名
} else if (resource == null && url == null && mapperClass != null) {
// 获得 Mapper 接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 添加到 configuration 中
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException( "A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}

遍历<mapper />标签的子节点

  1. 如果是<package />子节点,则获取package属性,对该包路径下的Mapper接口进行解析

  2. 否的的话,通过子节点的resource属性或者url属性解析该映射文件,或者通过class属性解析该Mapper接口

通常我们是直接配置一个包路径,这里就查看上面第1种对Mapper接口进行解析的方式,第2种的解析方式其实在第1 种方式都会涉及到,它只是抽取出来了,那么我们就直接看第1种方式

首先将package包路径添加到Configuration全局配置对象中,也就是往其内部的MapperRegistry注册表进行注册,调用它的MapperRegistryaddMappers(String packageName)方法进行注册

我们来看看在MapperRegistry注册表中是如何解析的,在之前文档的Binding模块中有讲到过这个类,该方法如下:

public class MapperRegistry {

    public void addMappers(String packageName) {
addMappers(packageName, Object.class);
} /**
* 用于扫描指定包中的Mapper接口,并与XML文件进行绑定
* @since 3.2.2
*/
public void addMappers(String packageName, Class<?> superType) {
// <1> 扫描指定包下的指定类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// <2> 遍历,添加到 knownMappers 中
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
} public <T> void addMapper(Class<T> type) {
// <1> 判断,必须是接口。
if (type.isInterface()) {
// <2> 已经添加过,则抛出 BindingException 异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// <3> 将Mapper接口对应的代理工厂添加到 knownMappers 中
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the mapper parser.
// If the type is already known, it won't try.
// <4> 解析 Mapper 的注解配置
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析 Mapper 接口上面的注解和 Mapper 接口对应的 XML 文件
parser.parse();
// <5> 标记加载完成
loadCompleted = true;
} finally {
// <6> 若加载未完成,从 knownMappers 中移除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}

<1>首先必须是个接口

<2>已经在MapperRegistry注册中心存在,则会抛出异常

<3>创建一个Mapper接口对应的MapperProxyFactory动态代理工厂

<4>重要!!!】通过MapperAnnotationBuilder解析该Mapper接口与对应XML映射文件

MapperAnnotationBuilder

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder:解析Mapper接口,主要是解析接口上面注解,加载XML文件会调用XMLMapperBuilder类进行解析

我们先来看看他的构造函数和parse()解析方法:

public class MapperAnnotationBuilder {

    /**
* 全局配置对象
*/
private final Configuration configuration;
/**
* Mapper 构造器小助手
*/
private final MapperBuilderAssistant assistant;
/**
* Mapper 接口的 Class 对象
*/
private final Class<?> type; public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
} public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 加载该接口对应的 XML 文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析 Mapper 接口的 @CacheNamespace 注解,创建缓存
parseCache();
// 解析 Mapper 接口的 @CacheNamespaceRef 注解,引用其他命名空间
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) { // 如果不是桥接方法
// 解析方法上面的注解
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
} private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
// 创建 XMLMapperBuilder 对象
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(),
xmlResource, configuration.getSqlFragments(), type.getName());
// 解析该 XML 文件
xmlParser.parse();
}
}
}
}

在构造函数中,会创建一个MapperBuilderAssistant对象,Mapper 构造器小助手,用于创建XML映射文件中对应相关对象

parse()方法,用于解析Mapper接口:

  1. 获取Mapper接口的名称,例如interface xxx.xxx.xxx,根据Configuration全局配置对象判断该Mapper接口是否被解析过

  2. 没有解析过则调用loadXmlResource()方法解析对应的XML映射文件

  3. 然后解析接口的@CacheNamespace和@CacheNamespaceRef注解,再依次解析方法上面的MyBatis相关注解

注解的相关解析这里就不讲述了,因为我们通常都是使用XML映射文件,逻辑没有特别复杂,都在MapperAnnotationBuilder中进行解析,感兴趣的小伙伴可以看看

精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件的更多相关文章

  1. 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  2. 精尽 MyBatis 源码分析 - MyBatis 初始化(一)之加载 mybatis-config.xml

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  3. 精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  4. 5.7 Liquibase:与具体数据库独立的追踪、管理和应用数据库Scheme变化的工具。-mybatis-generator将数据库表反向生成对应的实体类及基于mybatis的mapper接口和xml映射文件(类似代码生成器)

    一. liquibase 使用说明 功能概述:通过xml文件规范化维护数据库表结构及初始化数据. 1.配置不同环境下的数据库信息 (1)创建不同环境的数据库. (2)在resource/liquiba ...

  5. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  6. 精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  7. 6.Sentinel源码分析—Sentinel是如何动态加载配置限流的?

    Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Senti ...

  8. Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

    Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...

  9. angular源码分析:angular的整个加载流程

    在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...

随机推荐

  1. docker19.03限制容器使用的cpu资源

    一,用--cpus限制可用的cpu个数 例子: [root@localhost liuhongdi]# docker run -idt --name kafka1 --hostname kafka1 ...

  2. CentOS 8 Yum安装ansible

    ansible的安装 直接yum安装ansible会报错,需要先安装epel源. ansible的安装来源于epel仓库,因此在安装前需确保安装了正确的epel源. yum install -y ep ...

  3. CentOS 网卡固定地址配置

    修改4个文件后重启网卡 vim /etc/default/grub GRUB_CMDLINE_LINUX="resume=UUID=05dbb36b-dbba-40a3-ba99-1b044 ...

  4. linux mount 挂载提示 mount: you must specify the filesystem type

    解决方法: mkfs.ext3 /dev/vdv mount -t ext3 /dev/vdv /usr1

  5. Hexo相关配置

    date: 2018-11-16 18:27:14 updated: 2018-11-16 20:06:16 1.配置Hexo基本信息 title: 猫熊小才天の书院 #博客标题 subtitle: ...

  6. 部署SpringBoot项目jar包到云服务器

    前言 做安卓开发也有三四年了,但是对网络这块什么http.tcp/ip之类的一直不理解.并且想自己做一些小项目练练手的时候,数据库直接存在apk里总不是滋味,所以这次站在安卓开发的角度尝试着做一做简单 ...

  7. STM32入门系列-STM32最小系统介绍

    STM32最小系统组成 单片机最小系统,也就是能够使得单片机正常运行程序,最少需要连接哪些器件.一般来说,STM32最小系统由四部分组成: 电源电路 复位电路 晶振电路 下载电路 STM32单片机由A ...

  8. MONGODB03 - 分组计数_分组去重计数(基于 spring-data-mongodb)

    前因 项目中有查询MongoDB单表统计相关功能,涉及到MongoDB数据聚合相关操作,其中在多字段分组去重计数相关操作API上资料较少,spring-data-mongodb相关的API介绍也不够直 ...

  9. Day13 CSS样式

    1. css 选择器 1.1 css三种引入方法 <!DOCTYPE HTML> <html> <head> <meta charset="utf- ...

  10. 水题挑战2 :NOIP提高组 2011 聪明的质监员

    小T 是一名质量监督员,最近负责检验一批矿产的质量.这批矿产共有 \(n\) 个矿石,从\(1\) 到 \(n\) 逐一编号,每个矿石都有自己的重量 \(w_i\) 以及价值 \(v_i\) .检验矿 ...