code-learning/mybatis/16-mybatis-MyBatis 初始化(二)之加载 Mapper 映射配置文件.md

1436 lines
59 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 MyBatis 源码分析 —— MyBatis 初始化(二)之加载 Mapper 映射配置文件
# 1. 概述
本文接 [《精尽 MyBatis 源码分析 —— MyBatis 初始化(一)之加载 mybatis-config》](http://svip.iocoder.cn/MyBatis/builder-package-1) 一文,来分享 MyBatis 初始化的第二步,**加载 Mapper 映射配置文件**。而这个步骤的入口是 XMLMapperBuilder 。下面,我们一起来看看它的代码实现。
> FROM [《Mybatis3.3.x技术内幕Mybatis初始化流程》](https://my.oschina.net/zudajun/blog/668738)
>
> [![解析](16-mybatis-MyBatis 初始化(二)之加载 Mapper 映射配置文件.assets/01.png)](http://static.iocoder.cn/images/MyBatis/2020_02_13/01.png)解析
- 上图,就是 Mapper 映射配置文件的解析结果。
# 2. XMLMapperBuilder
`org.apache.ibatis.builder.xml.XMLMapperBuilder` ,继承 BaseBuilder 抽象类Mapper XML 配置构建器,主要负责解析 Mapper 映射配置文件。
## 2.1 构造方法
```
// XMLMapperBuilder.java
/**
* 基于 Java XPath 解析器
*/
private final XPathParser parser;
/**
* Mapper 构造器助手
*/
private final MapperBuilderAssistant builderAssistant;
/**
* 可被其他语句引用的可重用语句块的集合
*
* 例如:<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
*/
private final Map<String, XNode> sqlFragments;
/**
* 资源引用的地址
*/
private final String resource;
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
this(inputStream, configuration, resource, sqlFragments);
this.builderAssistant.setCurrentNamespace(namespace);
}
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
// 创建 MapperBuilderAssistant 对象
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
```
- `builderAssistant` 属性MapperBuilderAssistant 对象,是 XMLMapperBuilder 和 MapperAnnotationBuilder 的小助手,提供了一些公用的方法,例如创建 ParameterMap、MappedStatement 对象等等。关于 MapperBuilderAssistant 类,可见 [「3. MapperBuilderAssistant」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
## 2.2 parse
`#parse()` 方法,解析 Mapper XML 配置文件。代码如下:
```
// XMLMapperBuilder.java
public void parse() {
// <1> 判断当前 Mapper 是否已经加载过
if (!configuration.isResourceLoaded(resource)) {
// <2> 解析 `<mapper />` 节点
configurationElement(parser.evalNode("/mapper"));
// <3> 标记该 Mapper 已经加载过
configuration.addLoadedResource(resource);
// <4> 绑定 Mapper
bindMapperForNamespace();
}
// <5> 解析待定的 <resultMap /> 节点
parsePendingResultMaps();
// <6> 解析待定的 <cache-ref /> 节点
parsePendingCacheRefs();
// <7> 解析待定的 SQL 语句的节点
parsePendingStatements();
}
```
- `<1>` 处,调用 `Configuration#isResourceLoaded(String resource)` 方法,判断当前 Mapper 是否已经加载过。代码如下:
```
// Configuration.java
/**
* 已加载资源( Resource )集合
*/
protected final Set<String> loadedResources = new HashSet<>();
public boolean isResourceLoaded(String resource) {
return loadedResources.contains(resource);
}
```
- `<3>` 处,调用 `Configuration#addLoadedResource(String resource)` 方法,标记该 Mapper 已经加载过。代码如下:
```
// Configuration.java
public void addLoadedResource(String resource) {
loadedResources.add(resource);
}
```
- `<2>` 处,调用 `#configurationElement(XNode context)` 方法,解析 `<mapper />` 节点。详细解析,见 [「2.3 configurationElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<4>` 处,调用 `#bindMapperForNamespace()` 方法,绑定 Mapper 。详细解析,
- `<5>`、`<6>`、`<7>` 处,解析对应的**待定**的节点。详细解析,见 [「2.5 parsePendingXXX」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
## 2.3 configurationElement
`#configurationElement(XNode context)` 方法,解析 `<mapper />` 节点。代码如下:
```
// XMLMapperBuilder.java
private void configurationElement(XNode context) {
try {
// <1> 获得 namespace 属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// <1> 设置 namespace 属性
builderAssistant.setCurrentNamespace(namespace);
// <2> 解析 <cache-ref /> 节点
cacheRefElement(context.evalNode("cache-ref"));
// <3> 解析 <cache /> 节点
cacheElement(context.evalNode("cache"));
// 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// <4> 解析 <resultMap /> 节点们
resultMapElements(context.evalNodes("/mapper/resultMap"));
// <5> 解析 <sql /> 节点们
sqlElement(context.evalNodes("/mapper/sql"));
// <6> 解析 <select /> <insert /> <update /> <delete /> 节点们
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
```
- `<1>` 处,获得 `namespace` 属性,并设置到 MapperAnnotationBuilder 中。
- `<2>` 处,调用 `#cacheRefElement(XNode context)` 方法,解析 `<cache-ref />` 节点。详细解析,见 [「2.3.1 cacheElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<3>` 处,调用 `#cacheElement(XNode context)` 方法,解析 `cache />` 标签。详细解析,见 [「2.3.2 cacheElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<4>` 处,调用 `#resultMapElements(List<XNode> list)` 方法,解析 `<resultMap />` 节点们。详细解析,见 [「2.3.3 resultMapElements」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<5>` 处,调用 `#sqlElement(List<XNode> list)` 方法,解析 `<sql />` 节点们。详细解析,见 [「2.3.4 sqlElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<6>` 处,调用 `#buildStatementFromContext(List<XNode> list)` 方法,解析 `<select />`、`<insert />`、`<update />`、`<delete />` 节点们。详细解析,见 [「2.3.5 buildStatementFromContext」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
### 2.3.1 cacheElement
`#cacheRefElement(XNode context)` 方法,解析 `<cache-ref />` 节点。代码如下:
```
// XMLMapperBuilder.java
private void cacheRefElement(XNode context) {
if (context != null) {
// <1> 获得指向的 namespace 名字,并添加到 configuration 的 cacheRefMap 中
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
// <2> 创建 CacheRefResolver 对象,并执行解析
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
// <3> 解析失败,添加到 configuration 的 incompleteCacheRefs 中
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
```
- 示例如下:
```
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
```
- `<1>` 处,获得指向的 `namespace` 名字,并调用 `Configuration#addCacheRef(String namespace, String referencedNamespace)` 方法,添加到 `configuration` 的 `cacheRefMap` 中。代码如下:
```
// Configuration.java
/**
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*
* Cache 指向的映射
*
* @see #addCacheRef(String, String)
* @see org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheRefElement(XNode)
*/
protected final Map<String, String> cacheRefMap = new HashMap<>();
public void addCacheRef(String namespace, String referencedNamespace) {
cacheRefMap.put(namespace, referencedNamespace);
}
```
- `<2>` 处,创建 CacheRefResolver 对象,并调用 `CacheRefResolver#resolveCacheRef()` 方法,执行解析。关于 CacheRefResolver ,在 [「2.3.1.1 CacheRefResolver」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 详细解析。
- `<3>` 处,解析失败,因为此处指向的 Cache 对象可能未初始化,则先调用 `Configuration#addIncompleteCacheRef(CacheRefResolver incompleteCacheRef)` 方法,添加到 `configuration` 的 `incompleteCacheRefs` 中。代码如下:
```
// Configuration.java
/**
* CacheRefResolver 集合
*/
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
public void addIncompleteCacheRef(CacheRefResolver incompleteCacheRef) {
incompleteCacheRefs.add(incompleteCacheRef);
}
```
#### 2.3.1.1 CacheRefResolver
`org.apache.ibatis.builder.CacheRefResolver` Cache 指向解析器。代码如下:
```
// CacheRefResolver.java
public class CacheRefResolver {
private final MapperBuilderAssistant assistant;
/**
* Cache 指向的命名空间
*/
private final String cacheRefNamespace;
public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
this.assistant = assistant;
this.cacheRefNamespace = cacheRefNamespace;
}
public Cache resolveCacheRef() {
return assistant.useCacheRef(cacheRefNamespace);
}
}
```
- 在 `#resolveCacheRef()` 方法中,会调用 `MapperBuilderAssistant#useCacheRef(String namespace)` 方法,获得指向的 Cache 对象。详细解析,见 [「3.3 useCacheRef」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
### 2.3.2 cacheElement
`#cacheElement(XNode context)` 方法,解析 `cache />` 标签。代码如下:
```
// XMLMapperBuilder.java
private void cacheElement(XNode context) throws Exception {
if (context != null) {
// <1> 获得负责存储的 Cache 实现类
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// <2> 获得负责过期的 Cache 实现类
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// <3> 获得 flushInterval、size、readWrite、blocking 属性
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
// <4> 获得 Properties 属性
Properties props = context.getChildrenAsProperties();
// <5> 创建 Cache 对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
```
- 示例如下:
```
// 使用默认缓存
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
// 使用自定义缓存
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
```
- `<1>`、`<2>`、`<3>`、`<4>` 处,见代码注释即可。
- `<5>` 处,调用 `MapperBuilderAssistant#useNewCache(...)` 方法,创建 Cache 对象。详细解析,见 [「3.4 useNewCache」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 中。
### 2.3.3 resultMapElements
> 老艿艿:开始高能,保持耐心。
整体流程如下:
> FROM [《Mybatis3.3.x技术内幕Mybatis初始化流程》](https://my.oschina.net/zudajun/blog/669868)
>
> [![解析``](http://static.iocoder.cn/images/MyBatis/2020_02_13/03.png)](http://static.iocoder.cn/images/MyBatis/2020_02_13/03.png)解析``
`#resultMapElements(List<XNode> list)` 方法,解析 `<resultMap />` 节点们。代码如下:
```
// XMLMapperBuilder.java
// 解析 <resultMap /> 节点们
private void resultMapElements(List<XNode> list) throws Exception {
// 遍历 <resultMap /> 节点们
for (XNode resultMapNode : list) {
try {
// 处理单个 <resultMap /> 节点
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
// 解析 <resultMap /> 节点
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping>emptyList());
}
// 解析 <resultMap /> 节点
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// <1> 获得 id 属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// <1> 获得 type 属性
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// <1> 获得 extends 属性
String extend = resultMapNode.getStringAttribute("extends");
// <1> 获得 autoMapping 属性
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// <1> 解析 type 对应的类
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
// <2> 创建 ResultMapping 集合
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
// <2> 遍历 <resultMap /> 的子节点
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
// <2.1> 处理 <constructor /> 节点
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
// <2.2> 处理 <discriminator /> 节点
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
// <2.3> 处理其它节点
} else {
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// <3> 创建 ResultMapResolver 对象,执行解析
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
// <4> 解析失败,添加到 configuration 中
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
```
- `<resultMap />` 标签的解析,是相对复杂的过程,情况比较多,所以胖友碰到不懂的,可以看看 [《MyBatis 文档 —— Mapper XML 文件》](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 文档。
- `<1>` 处,获得 `id`、`type`、`extends`、`autoMapping` 属性,并解析 `type` 对应的类型。
- ```
<2>
```
处,创建 ResultMapping 集合,后遍历
```
<resultMap />
```
节点们,将每一个子节点解析成一个或多个 ResultMapping 对象,添加到集合中。即如下图所示:
![ResultMap 与 ResultMapping 的映射](16-mybatis-MyBatis 初始化(二)之加载 Mapper 映射配置文件.assets/02.png)
ResultMap 与 ResultMapping 的映射
- `<2.1>` 处,调用 `#processConstructorElement(...)` 方法,处理 `<constructor />` 节点。详细解析,见 [「2.3.3.1 processConstructorElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<2.2>` 处,调用 `#processDiscriminatorElement(...)` 方法,处理 `<discriminator />` 节点。详细解析,见 [「2.3.3.2 processDiscriminatorElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<2.3>` 处,调用 `#buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags)` 方法,将当前子节点构建成 ResultMapping 对象,并添加到 `resultMappings` 中。详细解析,见 [「2.3.3.3 buildResultMappingFromContext」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。🌞 这一块,和 [「2.3.3.1 processConstructorElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 的 `<3>` 是一致的。
- `<3>` 处,创建 ResultMapResolver 对象,执行解析。关于 ResultMapResolver ,在 「2.3.1.1 CacheRefResolver」 详细解析。
- `<4>` 处,如果解析失败,说明有依赖的信息不全,所以调用 `Configuration#addIncompleteResultMap(ResultMapResolver resultMapResolver)` 方法,添加到 Configuration 的 `incompleteResultMaps` 中。代码如下:
```
// Configuration.java
/**
* ResultMapResolver 集合
*/
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
public void addIncompleteResultMap(ResultMapResolver resultMapResolver) {
incompleteResultMaps.add(resultMapResolver);
}
```
#### 2.3.3.1 processConstructorElement
`#processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings)` 方法,处理 `<constructor />` 节点。代码如下:
```
// XMLMapperBuilder.java
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
// <1> 遍历 <constructor /> 的子节点们
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
// <2> 获得 ResultFlag 集合
List<ResultFlag> flags = new ArrayList<>();
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
// <3> 将当前子节点构建成 ResultMapping 对象,并添加到 resultMappings 中
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
```
- `<1>` 和 `<3>` 处,遍历 `<constructor />` 的子节点们,调用 `#buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags)` 方法,将当前子节点构建成 ResultMapping 对象,并添加到 `resultMappings` 中。详细解析,见 [「2.3.3.3 buildResultMappingFromContext」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<2>` 处,我们可以看到一个 `org.apache.ibatis.mapping.ResultFlag` 枚举类,结果标识。代码如下:
```
// ResultFlag.java
public enum ResultFlag {
/**
* ID
*/
ID,
/**
* 构造方法
*/
CONSTRUCTOR
}
```
- 具体的用途,见下文。
#### 2.3.3.2 processDiscriminatorElement
`#processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings)` 方法,处理 `<constructor />` 节点。代码如下:
```
// XMLMapperBuilder.java
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
// <1> 解析各种属性
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String typeHandler = context.getStringAttribute("typeHandler");
// <1> 解析各种属性对应的类
Class<?> javaTypeClass = resolveClass(javaType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// <2> 遍历 <discriminator /> 的子节点,解析成 discriminatorMap 集合
Map<String, String> discriminatorMap = new HashMap<>();
for (XNode caseChild : context.getChildren()) {
String value = caseChild.getStringAttribute("value");
String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings)); // <2.1>
discriminatorMap.put(value, resultMap);
}
// <3> 创建 Discriminator 对象
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
```
- 可能大家对 `<discriminator />` 标签不是很熟悉,可以打开 [《MyBatis 文档 —— Mapper XML 文件》](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 文档,然后下【鉴别器】。😈 当然,这块简单了解下就好,实际场景下,艿艿貌似都不知道它的存在,哈哈哈哈。
- `<1>` 处,解析各种属性以及属性对应的类。
- `<2>` 处,遍历 `<discriminator />` 的子节点,解析成 `discriminatorMap` 集合。
- `<2.1>` 处,如果是内嵌的 ResultMap 的情况,则调用 `#processNestedResultMappings(XNode context, List<ResultMapping> resultMappings)` 方法,处理**内嵌**的 ResultMap 的情况。代码如下:
```
// XMLMapperBuilder.java
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
if ("association".equals(context.getName())
|| "collection".equals(context.getName())
|| "case".equals(context.getName())) {
if (context.getStringAttribute("select") == null) {
// 解析,并返回 ResultMap
ResultMap resultMap = resultMapElement(context, resultMappings);
return resultMap.getId();
}
}
return null;
}
```
- 该方法,会“递归”调用 `#resultMapElement(XNode context, List<ResultMapping> resultMappings)` 方法,处理内嵌的 ResultMap 的情况。也就是返回到 [「2.3.3 resultMapElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 流程。
- `<3>` 处,调用 `MapperBuilderAssistant#buildDiscriminator(...)` 方法,创建 Discriminator 对象。详细解析,见 [「3.6 buildDiscriminator」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
#### 2.3.3.3 buildResultMappingFromContext
`#buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags)` 方法,将当前节点构建成 ResultMapping 对象。代码如下:
```
// XMLMapperBuilder.java
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
// <1> 获得各种属性
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
// <1> 获得各种属性对应的类
Class<?> javaTypeClass = resolveClass(javaType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// <2> 构建 ResultMapping 对象
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
```
- `<1>` 处,解析各种属性以及属性对应的类。
- `<2>` 处,调用 `MapperBuilderAssistant#buildResultMapping(...)` 方法,构建 ResultMapping 对象。详细解析,见 [「3.5 buildResultMapping」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
#### 2.3.3.4 ResultMapResolver
`org.apache.ibatis.builder.ResultMapResolver`ResultMap 解析器。代码如下:
```
// ResultMapResolver.java
public class ResultMapResolver {
private final MapperBuilderAssistant assistant;
/**
* ResultMap 编号
*/
private final String id;
/**
* 类型
*/
private final Class<?> type;
/**
* 继承自哪个 ResultMap
*/
private final String extend;
/**
* Discriminator 对象
*/
private final Discriminator discriminator;
/**
* ResultMapping 集合
*/
private final List<ResultMapping> resultMappings;
/**
* 是否自动匹配
*/
private final Boolean autoMapping;
public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
this.assistant = assistant;
this.id = id;
this.type = type;
this.extend = extend;
this.discriminator = discriminator;
this.resultMappings = resultMappings;
this.autoMapping = autoMapping;
}
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
}
```
- 在 `#resolve()` 方法中,会调用 `MapperBuilderAssistant#addResultMap(...)` 方法,创建 ResultMap 对象。详细解析,见 [「3.7 addResultMap」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
### 2.3.4 sqlElement
`#sqlElement(List<XNode> list)` 方法,解析 `<sql />` 节点们。代码如下:
```
// XMLMapperBuilder.java
private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
// 上面两块代码,可以简写成 sqlElement(list, configuration.getDatabaseId());
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
// <1> 遍历所有 <sql /> 节点
for (XNode context : list) {
// <2> 获得 databaseId 属性
String databaseId = context.getStringAttribute("databaseId");
// <3> 获得完整的 id 属性,格式为 `${namespace}.${id}` 。
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
// <4> 判断 databaseId 是否匹配
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// <5> 添加到 sqlFragments 中
sqlFragments.put(id, context);
}
}
}
```
- `<1>` 处,遍历所有 `<sql />` 节点,逐个处理。
- `<2>` 处,获得 `databaseId` 属性。
- `<3>` 处,获得完整的 `id` 属性,格式为 `${namespace}.${id}` 。
- `<4>` 处,调用 `#databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId)` 方法,判断 `databaseId` 是否匹配。代码如下:
```
// XMLMapperBuilder.java
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
// 如果不匹配,则返回 false
if (requiredDatabaseId != null) {
return requiredDatabaseId.equals(databaseId);
} else {
// 如果未设置 requiredDatabaseId ,但是 databaseId 存在,说明还是不匹配,则返回 false
// mmp ,写的好绕
if (databaseId != null) {
return false;
}
// skip this fragment if there is a previous one with a not null databaseId
// 判断是否已经存在
if (this.sqlFragments.containsKey(id)) {
XNode context = this.sqlFragments.get(id);
// 若存在,则判断原有的 sqlFragment 是否 databaseId 为空。因为,当前 databaseId 为空,这样两者才能匹配。
return context.getStringAttribute("databaseId") == null;
}
}
return true;
}
```
- `<5>` 处,添加到 `sqlFragments` 中。因为 `sqlFragments` 是来自 Configuration 的 `sqlFragments` 属性,所以相当于也被添加了。代码如下:
```
// Configuration.java
/**
* 可被其他语句引用的可重用语句块的集合
*
* 例如:<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
*/
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
```
### 2.3.5 buildStatementFromContext
`#buildStatementFromContext(List<XNode> list)` 方法,解析 `<select />`、`<insert />`、`<update />`、`<delete />` 节点们。代码如下:
```
// XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
// 上面两块代码,可以简写成 buildStatementFromContext(list, configuration.getDatabaseId());
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// <1> 遍历 <select /> <insert /> <update /> <delete /> 节点们
for (XNode context : list) {
// <1> 创建 XMLStatementBuilder 对象,执行解析
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// <2> 解析失败,添加到 configuration 中
configuration.addIncompleteStatement(statementParser);
}
}
}
```
- `<1>` 处,遍历 `<select />`、`<insert />`、`<update />`、`<delete />` 节点们,逐个创建 XMLStatementBuilder 对象,执行解析。关于 XMLStatementBuilder 类,我们放在下篇文章,详细解析。
- `<2>` 处,解析失败,调用 `Configuration#addIncompleteStatement(XMLStatementBuilder incompleteStatement)` 方法,添加到 `configuration` 中。代码如下:
```
// Configuration.java
/**
* XMLStatementBuilder 集合
*/
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
public void addIncompleteStatement(XMLStatementBuilder incompleteStatement) {
incompleteStatements.add(incompleteStatement);
}
```
## 2.4 bindMapperForNamespace
`#bindMapperForNamespace()` 方法,绑定 Mapper 。代码如下:
```
// XMLMapperBuilder.java
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
// <1> 获得 Mapper 映射配置文件对应的 Mapper 接口,实际上类名就是 namespace 。嘿嘿,这个是常识。
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
// <2> 不存在该 Mapper 接口,则进行添加
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
// <3> 标记 namespace 已经添加,避免 MapperAnnotationBuilder#loadXmlResource(...) 重复加载
configuration.addLoadedResource("namespace:" + namespace);
// <4> 添加到 configuration 中
configuration.addMapper(boundType);
}
}
}
}
```
- `<1>` 处,获得 Mapper 映射配置文件对应的 Mapper 接口,实际上类名就是 `namespace` 。嘿嘿,这个是常识。
- `<2>` 处,调用 `Configuration#hasMapper(Class<?> type)` 方法,判断若谷不存在该 Mapper 接口,则进行绑定。代码如下:
```
// Configuration.java
/**
* MapperRegistry 对象
*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public boolean hasMapper(Class<?> type) {
return mapperRegistry.hasMapper(type);
}
```
- `<3>` 处,调用 `Configuration#addLoadedResource(String resource)` 方法,标记 `namespace` 已经添加,避免 `MapperAnnotationBuilder#loadXmlResource(...)` 重复加载。代码如下:
```
// MapperAnnotationBuilder.java
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())) {
// ... 省略创建 XMLMapperBuilder ,进行解析的代码
}
}
```
- `<4>` 处,调用 `Configuration#addMapper(Class<T> type)` 方法,添加到 `configuration` 的 `mapperRegistry` 中。代码如下:
```
// Configuration.java
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
```
## 2.5 parsePendingXXX
有三个 parsePendingXXX 方法,代码如下:
```
// XMLMapperBuilder.java
private void parsePendingResultMaps() {
// 获得 ResultMapResolver 集合,并遍历进行处理
Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
synchronized (incompleteResultMaps) {
Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
while (iter.hasNext()) {
try {
// 执行解析
iter.next().resolve();
// 移除
iter.remove();
} catch (IncompleteElementException e) {
// ResultMap is still missing a resource...
// 解析失败,不抛出异常
}
}
}
}
private void parsePendingCacheRefs() {
// 获得 CacheRefResolver 集合,并遍历进行处理
Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
synchronized (incompleteCacheRefs) {
Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
while (iter.hasNext()) {
try {
// 执行解析
iter.next().resolveCacheRef();
// 移除
iter.remove();
} catch (IncompleteElementException e) {
// Cache ref is still missing a resource...
}
}
}
}
private void parsePendingStatements() {
// 获得 XMLStatementBuilder 集合,并遍历进行处理
Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
synchronized (incompleteStatements) {
Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
while (iter.hasNext()) {
try {
// 执行解析
iter.next().parseStatementNode();
// 移除
iter.remove();
} catch (IncompleteElementException e) {
// Statement is still missing a resource...
}
}
}
}
```
- 三个方法的逻辑思路基本一致1获得对应的集合2遍历集合执行解析3执行成功则移除出集合4执行失败忽略异常。
- 当然,实际上,此处还是可能有执行解析失败的情况,但是随着每一个 Mapper 配置文件对应的 XMLMapperBuilder 执行一次这些方法,逐步逐步就会被全部解析完。😈
# 3. MapperBuilderAssistant
`org.apache.ibatis.builder.MapperBuilderAssistant` ,继承 BaseBuilder 抽象类Mapper 构造器的小助手,提供了一些公用的方法,例如创建 ParameterMap、MappedStatement 对象等等。
## 3.1 构造方法
```
// MapperBuilderAssistant.java
/**
* 当前 Mapper 命名空间
*/
private String currentNamespace;
/**
* 资源引用的地址
*/
private final String resource;
/**
* 当前 Cache 对象
*/
private Cache currentCache;
/**
* 是否未解析成功 Cache 引用
*/
private boolean unresolvedCacheRef; // issue #676
public MapperBuilderAssistant(Configuration configuration, String resource) {
super(configuration);
ErrorContext.instance().resource(resource);
this.resource = resource;
}
```
- 实际上,😈 如果要不是为了 XMLMapperBuilder 和 MapperAnnotationBuilder 都能调用到这个公用方法,可能都不需要这个类。
## 3.2 setCurrentNamespace
`#setCurrentNamespace(String currentNamespace)` 方法,设置 `currentNamespace` 属性。代码如下:
```
// MapperBuilderAssistant.java
public void setCurrentNamespace(String currentNamespace) {
// 如果传入的 currentNamespace 参数为空,抛出 BuilderException 异常
if (currentNamespace == null) {
throw new BuilderException("The mapper element requires a namespace attribute to be specified.");
}
// 如果当前已经设置,并且还和传入的不相等,抛出 BuilderException 异常
if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
throw new BuilderException("Wrong namespace. Expected '"
+ this.currentNamespace + "' but found '" + currentNamespace + "'.");
}
// 设置
this.currentNamespace = currentNamespace;
}
```
## 3.3 useCacheRef
`#useCacheRef(String namespace)` 方法,获得指向的 Cache 对象。如果获得不到,则抛出 IncompleteElementException 异常。代码如下:
```
// MapperBuilderAssistant.java
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true; // 标记未解决
// <1> 获得 Cache 对象
Cache cache = configuration.getCache(namespace);
// 获得不到,抛出 IncompleteElementException 异常
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
// 记录当前 Cache 对象
currentCache = cache;
unresolvedCacheRef = false; // 标记已解决
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
```
- `<1>` 处,调用 `Configuration#getCache(String id)` 方法,获得 Cache 对象。代码如下:
```
// Configuration.java
/**
* Cache 对象集合
*
* KEY命名空间 namespace
*/
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
public Cache getCache(String id) {
return caches.get(id);
}
```
## 3.4 useNewCache
`#useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props)` 方法,创建 Cache 对象。代码如下:
```
// MapperBuilderAssistant.java
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
// <1> 创建 Cache 对象
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
// <2> 添加到 configuration 的 caches 中
configuration.addCache(cache);
// <3> 赋值给 currentCache
currentCache = cache;
return cache;
}
```
- `<1>` 处,创建 Cache 对象。关于 CacheBuilder 类,详细解析,见 [「3.4.1 CacheBuilder」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<2>` 处,调用 `Configuration#addCache(Cache cache)` 方法,添加到 `configuration` 的 `caches` 中。代码如下:
```
// Configuration.java
public void addCache(Cache cache) {
caches.put(cache.getId(), cache);
}
```
- `<3>` 处,赋值给 `currentCache` 。
### 3.4.1 CacheBuilder
`org.apache.ibatis.mapping.CacheBuilder` Cache 构造器。基于装饰者设计模式,进行 Cache 对象的构造。代码比较简单,但是有点略长,胖友直接点击 [链接](https://github.com/YunaiV/mybatis-3/blob/master/src/main/java/org/apache/ibatis/mapping/CacheBuilder.java) 查看,已经添加了完整的注释。
## 3.5 buildResultMapping
`#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
public ResultMapping 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) {
// <1> 解析对应的 Java Type 类和 TypeHandler 对象
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
// <2> 解析组合字段名称成 ResultMapping 集合。涉及「关联的嵌套查询」
List<ResultMapping> composites = parseCompositeColumnName(column);
// <3> 创建 ResultMapping 对象
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true)) // <3.1>
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true)) // <3.1>
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<>() : flags)
.composites(composites)
.notNullColumns(parseMultipleColumnNames(notNullColumn)) // <3.2>
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
}
```
- `<1>` 处,解析对应的 Java Type 类和 TypeHandler 对象。
- `<2>` 处,调用 `#parseCompositeColumnName(String columnName)` 方法,解析组合字段名称成 ResultMapping 集合。详细解析,见 [「3.5.1 parseCompositeColumnName」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 中。
- ```
<3>
```
处,创建 ResultMapping 对象。
- `<3.1>` 处,调用 `#applyCurrentNamespace(String base, boolean isReference)` 方法,拼接命名空间。详细解析,见 [「3.5.2 applyCurrentNamespace」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<3.2>` 处,调用 `#parseMultipleColumnNames(String notNullColumn)` 方法,将字符串解析成集合。详细解析,见 [「3.5.3 parseMultipleColumnNames」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- 关于 ResultMapping 类,在 [「3.5.4 ResultMapping」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 中详细解析。
### 3.5.1 parseCompositeColumnName
`#parseCompositeColumnName(String columnName)` 方法,解析组合字段名称成 ResultMapping 集合。代码如下:
```
// MapperBuilderAssistant.java
private List<ResultMapping> parseCompositeColumnName(String columnName) {
List<ResultMapping> composites = new ArrayList<>();
// 分词,解析其中的 property 和 column 的组合对
if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
while (parser.hasMoreTokens()) {
String property = parser.nextToken();
String column = parser.nextToken();
// 创建 ResultMapping 对象
ResultMapping complexResultMapping = new ResultMapping.Builder(
configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
// 添加到 composites 中
composites.add(complexResultMapping);
}
}
return composites;
}
```
- 对于这种情况,官方文档说明如下:
> FROM [《MyBatis 文档 —— Mapper XML 文件》](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 的 「关联的嵌套查询」 小节
>
> 来自数据库的列名,或重命名的列标签。这和通常传递给 resultSet.getString(columnName)方法的字符串是相同的。 column 注 意 : 要 处 理 复 合 主 键 , 你 可 以 指 定 多 个 列 名 通 过 column= ” {prop1=col1,prop2=col2} ” 这种语法来传递给嵌套查询语 句。这会引起 prop1 和 prop2 以参数对象形式来设置给目标嵌套查询语句。
- 😈 不用理解太细,如果胖友和我一样,基本用不到这个特性。
### 3.5.2 applyCurrentNamespace
`#applyCurrentNamespace(String base, boolean isReference)` 方法,拼接命名空间。代码如下:
```
// MapperBuilderAssistant.java
public String applyCurrentNamespace(String base, boolean isReference) {
if (base == null) {
return null;
}
if (isReference) {
// is it qualified with any namespace yet?
if (base.contains(".")) {
return base;
}
} else {
// is it qualified with this namespace yet?
if (base.startsWith(currentNamespace + ".")) {
return base;
}
if (base.contains(".")) {
throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
}
}
// 拼接 currentNamespace + base
return currentNamespace + "." + base;
}
```
- 通过这样的方式,生成**唯一**在的标识。
### 3.5.3 parseMultipleColumnNames
`#parseMultipleColumnNames(String notNullColumn)` 方法,将字符串解析成集合。代码如下:
```
// MapperBuilderAssistant.java
private Set<String> parseMultipleColumnNames(String columnName) {
Set<String> columns = new HashSet<>();
if (columnName != null) {
// 多个字段,使用 ,分隔
if (columnName.indexOf(',') > -1) {
StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
while (parser.hasMoreTokens()) {
String column = parser.nextToken();
columns.add(column);
}
} else {
columns.add(columnName);
}
}
return columns;
}
```
### 3.5.4 ResultMapping
`org.apache.ibatis.mapping.ResultMapping` ResultMap 中的每一条结果字段的映射。代码比较简单,但是有点略长,胖友直接点击 [链接](https://github.com/YunaiV/mybatis-3/blob/master/src/main/java/org/apache/ibatis/mapping/ResultMapping.java) 查看,已经添加了完整的注释。
## 3.6 buildDiscriminator
`#buildDiscriminator(Class<?> resultType, String column, Class<?> javaType, JdbcType jdbcType, Class<? extends TypeHandler<?>> typeHandler, Map<String, String> discriminatorMap)` 方法,构建 Discriminator 对象。代码如下:
```
// MapperBuilderAssistant.java
public Discriminator buildDiscriminator(
Class<?> resultType,
String column,
Class<?> javaType,
JdbcType jdbcType,
Class<? extends TypeHandler<?>> typeHandler,
Map<String, String> discriminatorMap) {
// 构建 ResultMapping 对象
ResultMapping resultMapping = buildResultMapping(
resultType,
null,
column,
javaType,
jdbcType,
null,
null,
null,
null,
typeHandler,
new ArrayList<ResultFlag>(),
null,
null,
false);
// 创建 namespaceDiscriminatorMap 映射
Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
String resultMap = e.getValue();
resultMap = applyCurrentNamespace(resultMap, true); // 生成 resultMap 标识
namespaceDiscriminatorMap.put(e.getKey(), resultMap);
}
// 构建 Discriminator 对象
return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
}
```
- 简单看看就好,`<discriminator />` 平时用的很少。
### 3.6.1 Discriminator
`org.apache.ibatis.mapping.Discriminator` ,鉴别器,代码比较简单,胖友直接点击 [链接](https://github.com/YunaiV/mybatis-3/blob/master/src/main/java/org/apache/ibatis/mapping/Discriminator.java) 查看,已经添加了完整的注释。
## 3.7 addResultMap
`#addResultMap(String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping)` 方法,创建 ResultMap 对象,并添加到 Configuration 中。代码如下:
```
// MapperBuilderAssistant.java
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
// <1> 获得 ResultMap 编号,即格式为 `${namespace}.${id}` 。
id = applyCurrentNamespace(id, false);
// <2.1> 获取完整的 extend 属性,即格式为 `${namespace}.${extend}` 。从这里的逻辑来看,貌似只能自己 namespace 下的 ResultMap 。
extend = applyCurrentNamespace(extend, true);
// <2.2> 如果有父类,则将父类的 ResultMap 集合,添加到 resultMappings 中。
if (extend != null) {
// <2.2.1> 获得 extend 对应的 ResultMap 对象。如果不存在,则抛出 IncompleteElementException 异常
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
// 获取 extend 的 ResultMap 对象的 ResultMapping 集合,并移除 resultMappings
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
// 判断当前的 resultMappings 是否有构造方法,如果有,则从 extendedResultMappings 移除所有的构造类型的 ResultMapping 们
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
}
// 将 extendedResultMappings 添加到 resultMappings 中
resultMappings.addAll(extendedResultMappings);
}
// <3> 创建 ResultMap 对象
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
// <4> 添加到 configuration 中
configuration.addResultMap(resultMap);
return resultMap;
}
```
- `<1>` 处,获得 ResultMap 编号,即格式为 `${namespace}.${id}` 。
- `<2.1>` 处,获取完整的 extend 属性,即格式为 `${namespace}.${extend}` 。**从这里的逻辑来看,貌似只能自己 namespace 下的 ResultMap** 。
- `<2.2>` 处,如果有父类,则将父类的 ResultMap 集合,添加到 `resultMappings` 中。逻辑有些绕,胖友耐心往下看。
- `<2.2.1>` 处,获得 `extend` 对应的 ResultMap 对象。如果不存在,则抛出 IncompleteElementException 异常。代码如下:
```
// Configuration.java
/**
* ResultMap 的映射
*
* KEY`${namespace}.${id}`
*/
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
public ResultMap getResultMap(String id) {
return resultMaps.get(id);
}
public boolean hasResultMap(String id) {
return resultMaps.containsKey(id);
}
```
- x
- `<3>` 处,创建 ResultMap 对象。详细解析,见 [「3.7.1 ResultMap」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。
- `<4>` 处, 调用 `Configuration#addResultMap(ResultMap rm)` 方法,添加到 Configuration 的 `resultMaps` 中。代码如下:
```
// Configuration.java
public void addResultMap(ResultMap rm) {
// <1> 添加到 resultMaps 中
resultMaps.put(rm.getId(), rm);
// 遍历全局的 ResultMap 集合,若其拥有 Discriminator 对象,则判断是否强制标记为有内嵌的 ResultMap
checkLocallyForDiscriminatedNestedResultMaps(rm);
// 若传入的 ResultMap 不存在内嵌 ResultMap 并且有 Discriminator ,则判断是否需要强制表位有内嵌的 ResultMap
checkGloballyForDiscriminatedNestedResultMaps(rm);
}
```
- `<1>` 处,添加到 `resultMaps` 中。
- `<2>` 处,调用 `#checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm)` 方法,遍历全局的 ResultMap 集合,若其拥有 Discriminator 对象,则判断是否强制标记为有内嵌的 ResultMap 。代码如下:
```
// Configuration.java
// Slow but a one time cost. A better solution is welcome.
protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
// 如果传入的 ResultMap 有内嵌的 ResultMap
if (rm.hasNestedResultMaps()) {
// 遍历全局的 ResultMap 集合
for (Map.Entry<String, ResultMap> entry : resultMaps.entrySet()) {
Object value = entry.getValue();
if (value != null) {
ResultMap entryResultMap = (ResultMap) value;
// 判断遍历的全局的 entryResultMap 不存在内嵌 ResultMap 并且有 Discriminator
if (!entryResultMap.hasNestedResultMaps() && entryResultMap.getDiscriminator() != null) {
// 判断是否 Discriminator 的 ResultMap 集合中,使用了传入的 ResultMap 。
// 如果是,则标记为有内嵌的 ResultMap
Collection<String> discriminatedResultMapNames = entryResultMap.getDiscriminator().getDiscriminatorMap().values();
if (discriminatedResultMapNames.contains(rm.getId())) {
entryResultMap.forceNestedResultMaps();
}
}
}
}
}
}
```
- 逻辑有点绕,胖友耐心看下去。。。
- `<3>` 处,调用 `#checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm)` 方法,若传入的 ResultMap 不存在内嵌 ResultMap 并且有 Discriminator ,则判断是否需要强制表位有内嵌的 ResultMap 。代码如下:
```
// Configuration.java
// Slow but a one time cost. A better solution is welcome.
protected void checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm) {
// 如果传入的 ResultMap 不存在内嵌 ResultMap 并且有 Discriminator
if (!rm.hasNestedResultMaps() && rm.getDiscriminator() != null) {
// 遍历传入的 ResultMap 的 Discriminator 的 ResultMap 集合
for (Map.Entry<String, String> entry : rm.getDiscriminator().getDiscriminatorMap().entrySet()) {
String discriminatedResultMapName = entry.getValue();
if (hasResultMap(discriminatedResultMapName)) {
// 如果引用的 ResultMap 存在内嵌 ResultMap ,则标记传入的 ResultMap 存在内嵌 ResultMap
ResultMap discriminatedResultMap = resultMaps.get(discriminatedResultMapName);
if (discriminatedResultMap.hasNestedResultMaps()) {
rm.forceNestedResultMaps();
break;
}
}
}
}
}
```
- 逻辑有点绕,胖友耐心看下去。。。整体逻辑,和 `#checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm)` 方法是**类似**的,互为“倒影”。
### 3.7.1 ResultMap
`org.apache.ibatis.mapping.ResultMap` ,结果集,例如 `<resultMap />` 解析后的对象。代码比较简单,但是有点略长,胖友直接点击 [链接](https://github.com/YunaiV/mybatis-3/blob/master/src/main/java/org/apache/ibatis/mapping/ResultMap.java) 查看,已经添加了完整的注释。
# 666. 彩蛋
😈 又写到早上 1 点 30 左右,哈哈哈哈。
涉及配置解析类的文章,往往写起来枯燥,胖友读起来也枯燥。当然,本文提供的配置示例非常少(自我吐槽),所以胖友先辛苦下,耐心搭配 [《MyBatis 文档 —— Mapper XML 文件》](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 一起看看。
如果胖友看到有疑问一定一定一定要来星球给艿艿提供。这样艿艿也好针对的优化这个文章。hohoho 。
参考和推荐如下文章:
- 祖大俊 [《Mybatis3.3.x技术内幕Mybatis初始化流程](https://my.oschina.net/zudajun/blog/668738)
- 祖大俊 [《Mybatis3.3.x技术内幕Mybatis初始化流程](https://my.oschina.net/zudajun/blog/668787)
- 祖大俊 [《Mybatis3.3.x技术内幕Mybatis初始化流程](https://my.oschina.net/zudajun/blog/669868)
- 田小波 [《MyBatis 源码分析 - 映射文件解析过程》](https://www.tianxiaobo.com/2018/07/30/MyBatis-源码分析-映射文件解析过程/)
- 徐郡明 [《MyBatis 技术内幕》](https://item.jd.com/12125531.html) 的 [「3.1 MyBatis 初始化」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 小节