本文接 《精尽 MyBatis 源码分析 —— MyBatis 初始化(一)之加载 mybatis-config》 一文,来分享 MyBatis 初始化的第二步,加载 Mapper 映射配置文件。而这个步骤的入口是 XMLMapperBuilder 。下面,我们一起来看看它的代码实现。
FROM 《Mybatis3.3.x技术内幕(八):Mybatis初始化流程(上)》
org.apache.ibatis.builder.xml.XMLMapperBuilder
,继承 BaseBuilder 抽象类,Mapper XML 配置构建器,主要负责解析 Mapper 映射配置文件。
// XMLMapperBuilder.java
|
builderAssistant
属性,MapperBuilderAssistant 对象,是 XMLMapperBuilder 和 MapperAnnotationBuilder 的小助手,提供了一些公用的方法,例如创建 ParameterMap、MappedStatement 对象等等。关于 MapperBuilderAssistant 类,可见 「3. MapperBuilderAssistant」 。#parse()
方法,解析 Mapper XML 配置文件。代码如下:
// XMLMapperBuilder.java
|
<1>
处,调用 Configuration#isResourceLoaded(String resource)
方法,判断当前 Mapper 是否已经加载过。代码如下:
// Configuration.java
|
<3>
处,调用 Configuration#addLoadedResource(String resource)
方法,标记该 Mapper 已经加载过。代码如下:
// Configuration.java
|
<2>
处,调用 #configurationElement(XNode context)
方法,解析 <mapper />
节点。详细解析,见 「2.3 configurationElement」 。
<4>
处,调用 #bindMapperForNamespace()
方法,绑定 Mapper 。详细解析,
<5>
、<6>
、<7>
处,解析对应的待定的节点。详细解析,见 「2.5 parsePendingXXX」 。
#configurationElement(XNode context)
方法,解析 <mapper />
节点。代码如下:
// XMLMapperBuilder.java
|
<1>
处,获得 namespace
属性,并设置到 MapperAnnotationBuilder 中。<2>
处,调用 #cacheRefElement(XNode context)
方法,解析 <cache-ref />
节点。详细解析,见 「2.3.1 cacheElement」 。<3>
处,调用 #cacheElement(XNode context)
方法,解析 cache />
标签。详细解析,见 「2.3.2 cacheElement」 。<4>
处,调用 #resultMapElements(List<XNode> list)
方法,解析 <resultMap />
节点们。详细解析,见 「2.3.3 resultMapElements」 。<5>
处,调用 #sqlElement(List<XNode> list)
方法,解析 <sql />
节点们。详细解析,见 「2.3.4 sqlElement」 。<6>
处,调用 #buildStatementFromContext(List<XNode> list)
方法,解析 <select />
、<insert />
、<update />
、<delete />
节点们。详细解析,见 「2.3.5 buildStatementFromContext」 。#cacheRefElement(XNode context)
方法,解析 <cache-ref />
节点。代码如下:
// XMLMapperBuilder.java
|
示例如下:
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
|
<1>
处,获得指向的 namespace
名字,并调用 Configuration#addCacheRef(String namespace, String referencedNamespace)
方法,添加到 configuration
的 cacheRefMap
中。代码如下:
// Configuration.java
|
<2>
处,创建 CacheRefResolver 对象,并调用 CacheRefResolver#resolveCacheRef()
方法,执行解析。关于 CacheRefResolver ,在 「2.3.1.1 CacheRefResolver」 详细解析。
<3>
处,解析失败,因为此处指向的 Cache 对象可能未初始化,则先调用 Configuration#addIncompleteCacheRef(CacheRefResolver incompleteCacheRef)
方法,添加到 configuration
的 incompleteCacheRefs
中。代码如下:
// Configuration.java
|
org.apache.ibatis.builder.CacheRefResolver
,Cache 指向解析器。代码如下:
// CacheRefResolver.java
|
#resolveCacheRef()
方法中,会调用 MapperBuilderAssistant#useCacheRef(String namespace)
方法,获得指向的 Cache 对象。详细解析,见 「3.3 useCacheRef」 。#cacheElement(XNode context)
方法,解析 cache />
标签。代码如下:
// XMLMapperBuilder.java
|
示例如下:
// 使用默认缓存
|
<1>
、<2>
、<3>
、<4>
处,见代码注释即可。
<5>
处,调用 MapperBuilderAssistant#useNewCache(...)
方法,创建 Cache 对象。详细解析,见 「3.4 useNewCache」 中。
老艿艿:开始高能,保持耐心。
整体流程如下:
FROM 《Mybatis3.3.x技术内幕(十):Mybatis初始化流程(下)》
#resultMapElements(List<XNode> list)
方法,解析 <resultMap />
节点们。代码如下:
// XMLMapperBuilder.java
|
<resultMap />
标签的解析,是相对复杂的过程,情况比较多,所以胖友碰到不懂的,可以看看 《MyBatis 文档 —— Mapper XML 文件》 文档。
<1>
处,获得 id
、type
、extends
、autoMapping
属性,并解析 type
对应的类型。
<2>
处,创建 ResultMapping 集合,后遍历 <resultMap />
的子节点们,将每一个子节点解析成一个或多个 ResultMapping 对象,添加到集合中。即如下图所示:
<2.1>
处,调用 #processConstructorElement(...)
方法,处理 <constructor />
节点。详细解析,见 「2.3.3.1 processConstructorElement」 。<2.2>
处,调用 #processDiscriminatorElement(...)
方法,处理 <discriminator />
节点。详细解析,见 「2.3.3.2 processDiscriminatorElement」 。<2.3>
处,调用 #buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags)
方法,将当前子节点构建成 ResultMapping 对象,并添加到 resultMappings
中。详细解析,见 「2.3.3.3 buildResultMappingFromContext」 。?? 这一块,和 「2.3.3.1 processConstructorElement」 的 <3>
是一致的。<3>
处,创建 ResultMapResolver 对象,执行解析。关于 ResultMapResolver ,在 「2.3.1.1 CacheRefResolver」 详细解析。
<4>
处,如果解析失败,说明有依赖的信息不全,所以调用 Configuration#addIncompleteResultMap(ResultMapResolver resultMapResolver)
方法,添加到 Configuration 的 incompleteResultMaps
中。代码如下:
// Configuration.java
|
#processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings)
方法,处理 <constructor />
节点。代码如下:
// XMLMapperBuilder.java
|
<1>
和 <3>
处,遍历 <constructor />
的子节点们,调用 #buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags)
方法,将当前子节点构建成 ResultMapping 对象,并添加到 resultMappings
中。详细解析,见 「2.3.3.3 buildResultMappingFromContext」 。
<2>
处,我们可以看到一个 org.apache.ibatis.mapping.ResultFlag
枚举类,结果标识。代码如下:
// ResultFlag.java
|
#processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings)
方法,处理 <constructor />
节点。代码如下:
// XMLMapperBuilder.java
|
可能大家对 <discriminator />
标签不是很熟悉,可以打开 《MyBatis 文档 —— Mapper XML 文件》 文档,然后下【鉴别器】。?? 当然,这块简单了解下就好,实际场景下,艿艿貌似都不知道它的存在,哈哈哈哈。
<1>
处,解析各种属性以及属性对应的类。
<2>
处,遍历 <discriminator />
的子节点,解析成 discriminatorMap
集合。
<2.1>
处,如果是内嵌的 ResultMap 的情况,则调用 #processNestedResultMappings(XNode context, List<ResultMapping> resultMappings)
方法,处理内嵌的 ResultMap 的情况。代码如下:
// XMLMapperBuilder.java
|
#resultMapElement(XNode context, List<ResultMapping> resultMappings)
方法,处理内嵌的 ResultMap 的情况。也就是返回到 「2.3.3 resultMapElement」 流程。<3>
处,调用 MapperBuilderAssistant#buildDiscriminator(...)
方法,创建 Discriminator 对象。详细解析,见 「3.6 buildDiscriminator」 。
#buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags)
方法,将当前节点构建成 ResultMapping 对象。代码如下:
// XMLMapperBuilder.java
|
<1>
处,解析各种属性以及属性对应的类。<2>
处,调用 MapperBuilderAssistant#buildResultMapping(...)
方法,构建 ResultMapping 对象。详细解析,见 「3.5 buildResultMapping」 。org.apache.ibatis.builder.ResultMapResolver
,ResultMap 解析器。代码如下:
// ResultMapResolver.java
|
#resolve()
方法中,会调用 MapperBuilderAssistant#addResultMap(...)
方法,创建 ResultMap 对象。详细解析,见 「3.7 addResultMap」 。#sqlElement(List<XNode> list)
方法,解析 <sql />
节点们。代码如下:
// XMLMapperBuilder.java
|
<1>
处,遍历所有 <sql />
节点,逐个处理。
<2>
处,获得 databaseId
属性。
<3>
处,获得完整的 id
属性,格式为 ${namespace}.${id}
。
<4>
处,调用 #databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId)
方法,判断 databaseId
是否匹配。代码如下:
// XMLMapperBuilder.java
|
<5>
处,添加到 sqlFragments
中。因为 sqlFragments
是来自 Configuration 的 sqlFragments
属性,所以相当于也被添加了。代码如下:
// Configuration.java
|
#buildStatementFromContext(List<XNode> list)
方法,解析 <select />
、<insert />
、<update />
、<delete />
节点们。代码如下:
// XMLMapperBuilder.java
|
<1>
处,遍历 <select />
、<insert />
、<update />
、<delete />
节点们,逐个创建 XMLStatementBuilder 对象,执行解析。关于 XMLStatementBuilder 类,我们放在下篇文章,详细解析。
<2>
处,解析失败,调用 Configuration#addIncompleteStatement(XMLStatementBuilder incompleteStatement)
方法,添加到 configuration
中。代码如下:
// Configuration.java
|
#bindMapperForNamespace()
方法,绑定 Mapper 。代码如下:
// XMLMapperBuilder.java
|
<1>
处,获得 Mapper 映射配置文件对应的 Mapper 接口,实际上类名就是 namespace
。嘿嘿,这个是常识。
<2>
处,调用 Configuration#hasMapper(Class<?> type)
方法,判断若谷不存在该 Mapper 接口,则进行绑定。代码如下:
// Configuration.java
|
<3>
处,调用 Configuration#addLoadedResource(String resource)
方法,标记 namespace
已经添加,避免 MapperAnnotationBuilder#loadXmlResource(...)
重复加载。代码如下:
// MapperAnnotationBuilder.java
|
<4>
处,调用 Configuration#addMapper(Class<T> type)
方法,添加到 configuration
的 mapperRegistry
中。代码如下:
// Configuration.java
|
有三个 parsePendingXXX 方法,代码如下:
// XMLMapperBuilder.java
|
org.apache.ibatis.builder.MapperBuilderAssistant
,继承 BaseBuilder 抽象类,Mapper 构造器的小助手,提供了一些公用的方法,例如创建 ParameterMap、MappedStatement 对象等等。
// MapperBuilderAssistant.java
|
#setCurrentNamespace(String currentNamespace)
方法,设置 currentNamespace
属性。代码如下:
// MapperBuilderAssistant.java
|
#useCacheRef(String namespace)
方法,获得指向的 Cache 对象。如果获得不到,则抛出 IncompleteElementException 异常。代码如下:
// MapperBuilderAssistant.java
|
<1>
处,调用 Configuration#getCache(String id)
方法,获得 Cache 对象。代码如下:
// Configuration.java
|
#useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props)
方法,创建 Cache 对象。代码如下:
// MapperBuilderAssistant.java
|
<1>
处,创建 Cache 对象。关于 CacheBuilder 类,详细解析,见 「3.4.1 CacheBuilder」 。
<2>
处,调用 Configuration#addCache(Cache cache)
方法,添加到 configuration
的 caches
中。代码如下:
// Configuration.java
|
<3>
处,赋值给 currentCache
。
org.apache.ibatis.mapping.CacheBuilder
,Cache 构造器。基于装饰者设计模式,进行 Cache 对象的构造。代码比较简单,但是有点略长,胖友直接点击 链接 查看,已经添加了完整的注释。
#buildResultMapping(Class<?> resultType, String property, String column,Class<?> javaType, JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix, Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn, boolean lazy)
方法,构造 ResultMapping 对象。代码如下:
// MapperBuilderAssistant.java
|
<1>
处,解析对应的 Java Type 类和 TypeHandler 对象。<2>
处,调用 #parseCompositeColumnName(String columnName)
方法,解析组合字段名称成 ResultMapping 集合。详细解析,见 「3.5.1 parseCompositeColumnName」 中。<3>
处,创建 ResultMapping 对象。
<3.1>
处,调用 #applyCurrentNamespace(String base, boolean isReference)
方法,拼接命名空间。详细解析,见 「3.5.2 applyCurrentNamespace」 。<3.2>
处,调用 #parseMultipleColumnNames(String notNullColumn)
方法,将字符串解析成集合。详细解析,见 「3.5.3 parseMultipleColumnNames」 。#parseCompositeColumnName(String columnName)
方法,解析组合字段名称成 ResultMapping 集合。代码如下:
// MapperBuilderAssistant.java
|
对于这种情况,官方文档说明如下:
FROM 《MyBatis 文档 —— Mapper XML 文件》 的 「关联的嵌套查询」 小节
来自数据库的列名,或重命名的列标签。这和通常传递给 resultSet.getString(columnName)方法的字符串是相同的。 column 注 意 : 要 处 理 复 合 主 键 , 你 可 以 指 定 多 个 列 名 通 过 column= ” {prop1=col1,prop2=col2} ” 这种语法来传递给嵌套查询语 句。这会引起 prop1 和 prop2 以参数对象形式来设置给目标嵌套查询语句。
?? 不用理解太细,如果胖友和我一样,基本用不到这个特性。
#applyCurrentNamespace(String base, boolean isReference)
方法,拼接命名空间。代码如下:
// MapperBuilderAssistant.java
|
#parseMultipleColumnNames(String notNullColumn)
方法,将字符串解析成集合。代码如下:
// MapperBuilderAssistant.java
|
org.apache.ibatis.mapping.ResultMapping
,ResultMap 中的每一条结果字段的映射。代码比较简单,但是有点略长,胖友直接点击 链接 查看,已经添加了完整的注释。
#buildDiscriminator(Class<?> resultType, String column, Class<?> javaType, JdbcType jdbcType, Class<? extends TypeHandler<?>> typeHandler, Map<String, String> discriminatorMap)
方法,构建 Discriminator 对象。代码如下:
// MapperBuilderAssistant.java
|
<discriminator />
平时用的很少。org.apache.ibatis.mapping.Discriminator
,鉴别器,代码比较简单,胖友直接点击 链接 查看,已经添加了完整的注释。
#addResultMap(String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping)
方法,创建 ResultMap 对象,并添加到 Configuration 中。代码如下:
// MapperBuilderAssistant.java
|
<1>
处,获得 ResultMap 编号,即格式为 ${namespace}.${id}
。
<2.1>
处,获取完整的 extend 属性,即格式为 ${namespace}.${extend}
。从这里的逻辑来看,貌似只能自己 namespace 下的 ResultMap 。
<2.2>
处,如果有父类,则将父类的 ResultMap 集合,添加到 resultMappings
中。逻辑有些绕,胖友耐心往下看。
<2.2.1>
处,获得 extend
对应的 ResultMap 对象。如果不存在,则抛出 IncompleteElementException 异常。代码如下:
// Configuration.java
|
<3>
处,创建 ResultMap 对象。详细解析,见 「3.7.1 ResultMap」 。
<4>
处, 调用 Configuration#addResultMap(ResultMap rm)
方法,添加到 Configuration 的 resultMaps
中。代码如下:
// Configuration.java
|
<1>
处,添加到 resultMaps
中。
<2>
处,调用 #checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm)
方法,遍历全局的 ResultMap 集合,若其拥有 Discriminator 对象,则判断是否强制标记为有内嵌的 ResultMap 。代码如下:
// Configuration.java
|
<3>
处,调用 #checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm)
方法,若传入的 ResultMap 不存在内嵌 ResultMap 并且有 Discriminator ,则判断是否需要强制表位有内嵌的 ResultMap 。代码如下:
// Configuration.java
|
#checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm)
方法是类似的,互为“倒影”。org.apache.ibatis.mapping.ResultMap
,结果集,例如 <resultMap />
解析后的对象。代码比较简单,但是有点略长,胖友直接点击 链接 查看,已经添加了完整的注释。
原文:https://www.cnblogs.com/siye1989/p/11622144.html