mapper.xml配置
mybatis-config.xml 配置文件中的<mappers>节点告诉 MyBatis去哪些位置查找映射配置文件。根据mybatis官网mapper有如下四种配置方式:
在mybatis源码中, XMLConfigBuilder.mapperElement(root.evalNode(“mappers”));负责解析节点,具体源码如下:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历<mappers>子节点进行解析处理
for (XNode child : parent.getChildren()) {
// 子节点是package
if ("package".equals(child.getName())) {
//获取package路径
String mapperPackage = child.getStringAttribute("name");
// 向Configuration的类成员变量MapperRegistry添加mapper接口
configuration.addMappers(mapperPackage);
} else {
// 获取 resource ,url,mapperClass
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//设置 ErrorContext的resource
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建XMLConfigBuilder对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析xml
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//设置 ErrorContext的url
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
// 创建XMLConfigBuilder对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
// 解析xml
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
//设置 ErrorContext的mapperClass
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 向Configuration的类成员变量MapperRegistry添加mapper接口
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
mapper.xml文件解析整体逻辑
由上述源码可知,真正负责解析mapper.xml的是XMLMapperBuilder ,具体执行的方法时XMLMapperBuilder.parse(),该方法具体逻辑如下:
public void parse() {
// 判断是否已经加载过该xml文件。Configuration定义一个Set用来维护加载到配置文件中的xml文件:protected final Set<String> loadedResources = new HashSet<>();
if (!configuration.isResourceLoaded(resource)) {
// 解析xml中的各种配置
configurationElement(parser.evalNode("/mapper"));
//解析完毕后向loadedResources 中添加mapper文件
configuration.addLoadedResource(resource);
//把 namespace(接口类型)和工厂类绑定起来,并向mapperRegistry添加该接口
bindMapperForNamespace();
}
// 移除Configuration 中 解析失败的<resultMap >节点
parsePendingResultMaps();
// 移除Configuration 中 解析失败的<cache-ref>节点
parsePendingCacheRefs();
// 移除Configuration 中 解析失败的XMLStatement即sql语句。
parsePendingStatements();
}
根据上述代码逻辑,mapper.xml解析过程主要分为如下两个方面:
1 mapper.xml各个节点解析
对mapper.xml各个节点解析的入口是configurationElement(parser.evalNode("/mapper")),该方法详细解析了xml中的各个节点和配置。源码如下:
// 解析mapper.xml中所以的子标签。
private void configurationElement(XNode context) {
try {
// 获取xml的namespace
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//记录当前命名空间
builderAssistant.setCurrentNamespace(namespace);
//对<cache-ref>节点进行解析
cacheRefElement(context.evalNode("cache-ref"));
//对<cache>节点进行解析
cacheElement(context.evalNode("cache"));
//对<parameterMap>节点进行解析,该节点目前已废弃
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//对<resultMap>节点进行解析
resultMapElements(context.evalNodes("/mapper/resultMap"));
//对<sql>节点进行解析
sqlElement(context.evalNodes("/mapper/sql"));
//对增删改查四大节点进行解析,该方法最终结果是构建MappedStatement 对象
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 mapper接口绑定mapper.xml
在XMLMapperBuilder.bindMapperForNamespace()方法中,完成了映射配置文件与对应 Mapper 接口的绑定,具体实现如下:
private void bindMapperForNamespace() {
// 获取当前命名空间namespace
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 解析命名空间对应的接口类
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
// Configuration中是否已经加载过该接口
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
//向Configuration中添加该namespace
configuration.addLoadedResource("namespace:" + namespace);
//向Configuration中mapperRegistry注册该接口
configuration.addMapper(boundType);
}
}
}
}
总结:通过对mapper.xml解析的源码分析,我们不难得出xml解析的过程,其实就是配置组装Configuration对象的过程,该对象里几乎包含了全部的配置信息,该对象贯穿于sql执行始末,在mybatis中举足轻重。