mybatis-mapper.xml文件解析

1. 概述

本文接 《精尽 MyBatis 源码分析 —— MyBatis 初始化(一)之加载 mybatis-config》 一文,来分享 MyBatis 初始化的第二步,加载 Mapper 映射配置文件。而这个步骤的入口是 XMLMapperBuilder 。下面,我们一起来看看它的代码实现。

FROM 《Mybatis3.3.x技术内幕(八):Mybatis初始化流程(上)》

mybatis-mapper.xml文件解析

  • 上图,就是 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」 。

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」 。

  • <4> 处,调用 #bindMapperForNamespace() 方法,绑定 Mapper 。详细解析,

  • <5><6><7> 处,解析对应的待定的节点。详细解析,见 「2.5 parsePendingXXX」 。

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」 。
  • <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」 。

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」 详细解析。

  • <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」 。

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」 中。

2.3.3 resultMapElements

老艿艿:开始高能,保持耐心。

整体流程如下:

FROM 《Mybatis3.3.x技术内幕(十):Mybatis初始化流程(下)》

mybatis-mapper.xml文件解析

#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 文件》 文档。

  • <1> 处,获得 idtypeextendsautoMapping 属性,并解析 type 对应的类型。

  • <2> 处,创建 ResultMapping 集合,后遍历 <resultMap /> 的子节点们,将每一个子节点解析成一个或多个 ResultMapping 对象,添加到集合中。即如下图所示:mybatis-mapper.xml文件解析

上一篇:SQL映射文件


下一篇:MyBatis之ResultMap的association和collection标签(一)