从上篇可看到,通过 mapper 节点的配置,可指定 Mapper.xml 文件的添加。那么,Mapper.xml 的解析过程又是怎样的呢?
- 老规矩,双篇同线,同时打开 Mybatis 之 mapper.xml 子节点的详解篇。
1. Mapper.xml 文件的解析
1.1 通过指定 xml 文件添加 Mapper 映射
- 代表为通过 resource、url 指定,底层调用 XMLMapperBuilder 类的 parse() 方法
/* resource 相关源码 */
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
/* url 相关源码 */
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
- parse():通过 XMLMapperBuilder 类解析 mapper.xml 的节点信息,可看到子节点解析为 configurationElement(),点击 2.Mapper.xml 的节点信息解析 查看细节。
/* isResourceLoaded(): 文件是否已解析
* configurationElement(): 解析 mapper 节点信息
* addLoadedResource(): 添加 resource 文件为已解析
* bindMapperForNamespace(): 通过 xml 文件的 namespace 属性绑定 mapper 接口
* parsePendingResultMaps(): 解析未能正常完成解析的 ResultMap 节点
* parsePendingCacheRefs(): 解析未能正常完成解析的 CacheRef 节点
* parsePendingStatements(): 解析未能正常完成解析的 Statement
* */
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
1.2 通过指定 Mapper 类添加 Mapper 映射
- 代表为通过 packageName、class 指定
/* packageName、class 的相关源码 */
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
- parse(): 通过 MapperAnnotationBuilder 类解析 xml 文件的节点信息
/* loadXmlResource(): 读取 xml 文件资源,底层回到 1.1 的xml文件解析
* addLoadedResource(): 将资源设置为已解析
* setCurrentNamespace: 设置当前的命名空间
* parseCache(): Cache 的注解解析
* parseCacheRef(): CacheRef 的注解解析
* parsePendingMethods(): 解析先前解析失败的方法
*/
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
/* 获取 type 的所有方法,若非桥方法,解析该方法,解析失败添加进 IncompleteMethods */
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();
}
2. Mapper.xml 的节点信息解析
- Mapper.xml 解析节点的代码如下
private void configurationElement(XNode context) {
try {
/* namespace 不能为空,否则找不到对应的 Mapper,也就没有意义了 */
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
/* 设置当前 namesapce,即明确当前解析的是哪个 Mapper 文件 */
builderAssistant.setCurrentNamespace(namespace);
/* 以下为 mapper子节点的解析 */
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
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);
}
}
2.1 cache-ref
- cache-ref 的 xml 代码示例:通过 namespace 指定一个 Mapper,使用其 cache 配置
<cache-ref namespace<