该系列文档是本人在学习 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初始化过程中,大致会有以下几个步骤:
创建
Configuration
全局配置对象,会往TypeAliasRegistry
别名注册中心添加Mybatis需要用到的相关类,并设置默认的语言驱动类为XMLLanguageDriver
加载
mybatis-config.xml
配置文件、Mapper接口中的注解信息和XML映射文件,解析后的配置信息会形成相应的对象并保存到Configuration全局配置对象中构建
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 />
标签的子节点
如果是
<package />
子节点,则获取package
属性,对该包路径下的Mapper接口进行解析否的的话,通过子节点的
resource
属性或者url
属性解析该映射文件,或者通过class
属性解析该Mapper接口
通常我们是直接配置一个包路径,这里就查看上面第1
种对Mapper接口进行解析的方式,第2
种的解析方式其实在第1
种方式都会涉及到,它只是抽取出来了,那么我们就直接看第1
种方式
首先将package
包路径添加到Configuration
全局配置对象中,也就是往其内部的MapperRegistry
注册表进行注册,调用它的MapperRegistry
的addMappers(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接口:
获取Mapper接口的名称,例如
interface xxx.xxx.xxx
,根据Configuration全局配置对象判断该Mapper接口是否被解析过没有解析过则调用
loadXmlResource()
方法解析对应的XML映射文件然后解析接口的@CacheNamespace和@CacheNamespaceRef注解,再依次解析方法上面的MyBatis相关注解
注解的相关解析这里就不讲述了,因为我们通常都是使用XML映射文件,逻辑没有特别复杂,都在MapperAnnotationBuilder
中进行解析,感兴趣的小伙伴可以看看
精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件的更多相关文章
-
精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
-
精尽 MyBatis 源码分析 - MyBatis 初始化(一)之加载 mybatis-config.xml
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
-
精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
-
5.7 Liquibase:与具体数据库独立的追踪、管理和应用数据库Scheme变化的工具。-mybatis-generator将数据库表反向生成对应的实体类及基于mybatis的mapper接口和xml映射文件(类似代码生成器)
一. liquibase 使用说明 功能概述:通过xml文件规范化维护数据库表结构及初始化数据. 1.配置不同环境下的数据库信息 (1)创建不同环境的数据库. (2)在resource/liquiba ...
-
MyBatis源码分析-MyBatis初始化流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
-
精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
-
6.Sentinel源码分析—Sentinel是如何动态加载配置限流的?
Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Senti ...
-
Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...
-
angular源码分析:angular的整个加载流程
在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...
随机推荐
-
记录centos6.8安装Oracle10.2.0.1过程中的错误解决
[root@hadoop01 database]# ./runInstaller ./runInstaller: /opt/database/install/.oui: /lib/ld-linux.s ...
-
[ASE][Daily Scrum]11.30
燃烧图的页面进不去了…… 小结一下吧,sprint2的内容已经基本完成了, 推迟到之后进行的任务: ·地图块的刷新 一些bug尚未修复不过不是特别重要所以也推到后面了, 之后两个sprint主要会增加 ...
-
Android程序设计-圆形图片的实现
在android中,google只提供了对图形的圆形操作,而没有实现对图片的圆形操作,所以我们无法实现上述操作,在此我们将使用框架进行设计(下述框架为as编写): https://github.com ...
-
【leetcode】First Missing Positive
First Missing Positive Given an unsorted integer array, find the first missing positive integer. For ...
-
Hadoop虽然强大,但不是万能的(CSDN)
Hadoop很强大,但企业在使用Hadoop或者大数据之前,首先要明确自己的目标,再确定是否选对了工具,毕竟Hadoop不是万能的!本文中列举了几种不适合使用Hadoop的场景. 随着 Hadoop ...
-
[500lines]500行代码写web server
项目地址:https://github.com/aosabook/500lines/tree/master/web-server.作者是来自Mozilla的Greg Wilson.项目是用py2写成. ...
-
2018-2019-2 网络对抗技术 20165323 Exp6 信息搜集与漏洞扫描
一.实验内容 二.实验步骤 1.各种搜索技巧的应用 2.DNS IP注册信息的查询 3.基本的扫描技术 主机发现 端口扫描 OS及服务版本探测 具体服务的查点 4.漏洞扫描 三.实验中遇到的问题 四. ...
-
MFC停靠窗口实现(CDockablePane)
工作中编写MFC界面程序时用到了停靠窗口,为了避免之后用到时再去查询,这里记录下. 步骤 1.定义一个继承自CDockablePane的类 Class CDockableTest : public C ...
-
Egret 之 消除游戏 开发 PART 6 Egret elimination game development PART 6
Egret 之 消除游戏 开发 PART 6 Egret elimination game development PART 6 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱: ...
-
Cracking The Coding Interview 1.4
//Write a method to decide if two strings are anagrams or not. // // 变位词(anagrams)指的是组成两个单词的字符相同,但位置 ...