映射文件用于配置 SQL 语句,字段映射关系等。映射文件中包含< cache >、< cache-ref >、< resultMap >、< sql >、 <select|insert|update|delete>等二级节点,这些节点将在接下来内容中进行分析。
1、映射文件解析解析入口
映射文件的解析过程是配置文件解析过程的一部分,MyBatis 会在解析配置文件的过程 中对映射文件进行解析。解析逻辑封装在 mapperElement 方法中,下面来看一下。
// -☆- XMLConfigBuilder
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 获取 <package> 节点中的 name 属性
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 从指定包中查找 mapper 接口,并根据 mapper 接口解析映射配置
configuration.addMappers(mapperPackage);
} else {
// 获取 resource/url/class 等属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// resource 不为空,且其他两者为空,则从指定路径中加载配置
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
// url 不为空,且其他两者为空,则通过 url 加载配置
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
// 解析映射文件
mapperParser.parse();
// mapperClass不为空,且其他两者为空,
// 则通过mapperClass解析映射配置
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
上面代码的主要逻辑是遍历 mappers 的子节点,并根据节点属性值判断通过何种方式加 载映射文件或映射信息。这里把配置在注解中的内容称为映射信息,以 XML 为载体的配置 称为映射文件。
在 MyBatis 中,共有四种加载映射文件或映射信息的方式。
- 第一种是从文件 系统中加载映射文件;
- 第二种是通过 URL 的方式加载映射文件;
- 第三种是通过 mapper 接口 加载映射信息,映射信息可以配置在注解中,也可以配置在映射文件中。
- 第四种是通过包 扫描的方式获取到某个包下的所有类,并使用第三种方式为每个类解析映射信息。
下面开始分析映射文件的解析过程,在分析之前,先来看一下映射文件解析入口。
public void parse() {
// 检测映射文件是否已经被解析过
if (!configuration.isResourceLoaded(resource)) {
// 解析 mapper 节点
configurationElement(parser.evalNode("/mapper"));
// 添加资源路径到“已解析资源集合”中
configuration.addLoadedResource(resource);
// 通过命名空间绑定 Mapper 接口
bindMapperForNamespace();
}
// 处理未完成解析的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
映射文件解析入口逻辑包含三个核心操作,如下:
- 解析 mapper 节点
- 通过命名空间绑定 Mapper 接口
- 处理未完成解析的节点