# 精尽 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; /** * 可被其他语句引用的可重用语句块的集合 * * 例如: ${alias}.id,${alias}.username,${alias}.password */ private final Map sqlFragments; /** * 资源引用的地址 */ private final String resource; public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map sqlFragments, String namespace) { this(inputStream, configuration, resource, sqlFragments); this.builderAssistant.setCurrentNamespace(namespace); } public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); } private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map 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> 解析 `` 节点 configurationElement(parser.evalNode("/mapper")); // <3> 标记该 Mapper 已经加载过 configuration.addLoadedResource(resource); // <4> 绑定 Mapper bindMapperForNamespace(); } // <5> 解析待定的 节点 parsePendingResultMaps(); // <6> 解析待定的 节点 parsePendingCacheRefs(); // <7> 解析待定的 SQL 语句的节点 parsePendingStatements(); } ``` - `<1>` 处,调用 `Configuration#isResourceLoaded(String resource)` 方法,判断当前 Mapper 是否已经加载过。代码如下: ``` // Configuration.java /** * 已加载资源( Resource )集合 */ protected final Set 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)` 方法,解析 `` 节点。详细解析,见 [「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)` 方法,解析 `` 节点。代码如下: ``` // 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> 解析 节点 cacheRefElement(context.evalNode("cache-ref")); // <3> 解析 节点 cacheElement(context.evalNode("cache")); // 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // <4> 解析 节点们 resultMapElements(context.evalNodes("/mapper/resultMap")); // <5> 解析 节点们 sqlElement(context.evalNodes("/mapper/sql")); // <6> 解析 `、``、``、`` 节点们。详细解析,见 [「2.3.5 buildStatementFromContext」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。 ### 2.3.1 cacheElement `#cacheRefElement(XNode context)` 方法,解析 `` 节点。代码如下: ``` // 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); } } } ``` - 示例如下: ``` ``` - `<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 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 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 typeClass = typeAliasRegistry.resolveAlias(type); // <2> 获得负责过期的 Cache 实现类 String eviction = context.getStringAttribute("eviction", "LRU"); Class 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); } } ``` - 示例如下: ``` // 使用默认缓存 // 使用自定义缓存 ``` - `<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 list)` 方法,解析 `` 节点们。代码如下: ``` // XMLMapperBuilder.java // 解析 节点们 private void resultMapElements(List list) throws Exception { // 遍历 节点们 for (XNode resultMapNode : list) { try { // 处理单个 节点 resultMapElement(resultMapNode); } catch (IncompleteElementException e) { // ignore, it will be retried } } } // 解析 节点 private ResultMap resultMapElement(XNode resultMapNode) throws Exception { return resultMapElement(resultMapNode, Collections.emptyList()); } // 解析 节点 private ResultMap resultMapElement(XNode resultMapNode, List 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 resultMappings = new ArrayList<>(); resultMappings.addAll(additionalResultMappings); // <2> 遍历 的子节点 List resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { // <2.1> 处理 节点 if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); // <2.2> 处理 节点 } else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); // <2.3> 处理其它节点 } else { List 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; } } ``` - `` 标签的解析,是相对复杂的过程,情况比较多,所以胖友碰到不懂的,可以看看 [《MyBatis 文档 —— Mapper XML 文件》](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 文档。 - `<1>` 处,获得 `id`、`type`、`extends`、`autoMapping` 属性,并解析 `type` 对应的类型。 - ``` <2> ``` 处,创建 ResultMapping 集合,后遍历 ``` ``` 的 子 节点们,将每一个子节点解析成一个或多个 ResultMapping 对象,添加到集合中。即如下图所示: ![ResultMap 与 ResultMapping 的映射](16-mybatis-MyBatis 初始化(二)之加载 Mapper 映射配置文件.assets/02.png) ResultMap 与 ResultMapping 的映射 - `<2.1>` 处,调用 `#processConstructorElement(...)` 方法,处理 `` 节点。详细解析,见 [「2.3.3.1 processConstructorElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。 - `<2.2>` 处,调用 `#processDiscriminatorElement(...)` 方法,处理 `` 节点。详细解析,见 [「2.3.3.2 processDiscriminatorElement」](https://svip.iocoder.cn/MyBatis/builder-package-2/#) 。 - `<2.3>` 处,调用 `#buildResultMappingFromContext(XNode context, Class resultType, List 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 incompleteResultMaps = new LinkedList<>(); public void addIncompleteResultMap(ResultMapResolver resultMapResolver) { incompleteResultMaps.add(resultMapResolver); } ``` #### 2.3.3.1 processConstructorElement `#processConstructorElement(XNode resultChild, Class resultType, List resultMappings)` 方法,处理 `` 节点。代码如下: ``` // XMLMapperBuilder.java private void processConstructorElement(XNode resultChild, Class resultType, List resultMappings) throws Exception { // <1> 遍历 的子节点们 List argChildren = resultChild.getChildren(); for (XNode argChild : argChildren) { // <2> 获得 ResultFlag 集合 List 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>` 处,遍历 `` 的子节点们,调用 `#buildResultMappingFromContext(XNode context, Class resultType, List 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 resultMappings)` 方法,处理 `` 节点。代码如下: ``` // XMLMapperBuilder.java private Discriminator processDiscriminatorElement(XNode context, Class resultType, List 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> typeHandlerClass = resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); // <2> 遍历 的子节点,解析成 discriminatorMap 集合 Map 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); } ``` - 可能大家对 `` 标签不是很熟悉,可以打开 [《MyBatis 文档 —— Mapper XML 文件》](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 文档,然后下【鉴别器】。😈 当然,这块简单了解下就好,实际场景下,艿艿貌似都不知道它的存在,哈哈哈哈。 - `<1>` 处,解析各种属性以及属性对应的类。 - `<2>` 处,遍历 `` 的子节点,解析成 `discriminatorMap` 集合。 - `<2.1>` 处,如果是内嵌的 ResultMap 的情况,则调用 `#processNestedResultMappings(XNode context, List resultMappings)` 方法,处理**内嵌**的 ResultMap 的情况。代码如下: ``` // XMLMapperBuilder.java private String processNestedResultMappings(XNode context, List 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 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 flags)` 方法,将当前节点构建成 ResultMapping 对象。代码如下: ``` // XMLMapperBuilder.java private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List 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> 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 resultMappings; /** * 是否自动匹配 */ private final Boolean autoMapping; public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class type, String extend, Discriminator discriminator, List 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 list)` 方法,解析 `` 节点们。代码如下: ``` // XMLMapperBuilder.java private void sqlElement(List list) throws Exception { if (configuration.getDatabaseId() != null) { sqlElement(list, configuration.getDatabaseId()); } sqlElement(list, null); // 上面两块代码,可以简写成 sqlElement(list, configuration.getDatabaseId()); } private void sqlElement(List list, String requiredDatabaseId) throws Exception { // <1> 遍历所有 节点 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>` 处,遍历所有 `` 节点,逐个处理。 - `<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 /** * 可被其他语句引用的可重用语句块的集合 * * 例如: ${alias}.id,${alias}.username,${alias}.password */ protected final Map sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers"); ``` ### 2.3.5 buildStatementFromContext `#buildStatementFromContext(List list)` 方法,解析 ` 节点们 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>` 处,遍历 `