MyBatis之映射文件解析_概述
配置文件中映射文件的配置
mybatis配置文件中用子标签mappers定义要用到的映射文件,它有两个子标签mapper及package,mapper子标签定义一个具体的XML映射文件,而在纯注解的方式下,使用package定义一个包名,这个包下的所有类都作为映射接口类,类中用注解定义了SQL语句。
<mappers>
<!-- 定义所需的映射文件 -->
<!-- mapper可以有resource,url及class三个属性 -->
<mapper resource="mappers/OrderMapper.xml" />
<mapper resource="mappers/CustomerMapper.xml"/>
<!-- 纯注解的方法,这是包名,将这个包下的每个类都作为映射接口类 -->
<package name="com.kclm.owep.mapper"></package>
</mappers>
映射文件解析入口
mybatis在启动时,即会解析配置文件中的映射文件,当解析到配置文件的子标签mappers时,调用XMLConfigBuilder中的方法mapperElement开始处理配置文件中的mappers元素。
private void mapperElement(XNode parent)方法功能:
若子元素是mapper,则获取到mapper子标签的属性resource、url及class,这三个属性只能定义一个,并且必须定义一个
resource的值是一个本地的XML映射文件,url也类似,而class的属性值则是一个映射接口的类名,对应着使用纯注解的映射方式。
若是resource或url,则获取到对应的资源(一般是XML配置文件对应的InputStream),创建XMLMapperBuilder对象,在XML配置下,这个对象负责解析映射文件中的配置,其方法是public void parse()
属性class的值应该是映射接口的类名,此时调用Configuration对象有的addMapper方法,将这个类名加到MapperRegistry注册器中,Configuration对象持有MapperRegistry,具体加到映射注册器由MapperRegistry的方法public
private void mapperElement(XNode parent)方法源代码:
// 解析配置文件中的mappers节点
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 处理映射包,映射使用注解方式
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// resource,url及mapperClass必须有一个为空,并且不能同时不空
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 获得映射文件输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建XMLMapperBuilder对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 使用parse方法解析映射文件
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
// 这里创建了XMLMapperBuilder对象,由它负责解析映射文件中的元素
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} 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.");
}
}
}
}
}
XMLMapperBuilder类
XMLMapperBuilder用来解析Mybatis映射文件,与XMLConfigBuilder类似,它也是BaseBuilder的子类,负责映射文件的解析,节点的解析仍然使用XPathParser对象,同时使用MapperBuilderAssistant辅助类,用于缓存、sql参数、查询返回的结果集处理 。
XMLMapperBuilder主要持有Configuration,XPathParser及MapperBuilderAssistant等对象,它的主要功能就是解析映射文件,并将解析结果装配到Configuration对象,其入口方法为public void parse()
// 继承BaseBuilder
public class XMLMapperBuilder extends BaseBuilder {
// 使用XPathParser解析XML文档节点
private final XPathParser parser;
// 辅助类,用于缓存、sql参数、查询返回的结果集处理
private final MapperBuilderAssistant builderAssistant;
private final Map<String, XNode> sqlFragments;
private final String resource;
}
XMLMapperBuilder类的parse方法
在XMLConfigBuilder解析到mappers子标签,如果要解析XML映射文件,则会创建XMLMapperBuilder对象,并调用parse()方法解析映射文件。
parse主要功能:
先判断要解析的资源(resource)是否已经解析,在Configuration中有一个资源已解析的集合loadedResources,以此判断资源是否已加载,若没有加载,则调用方法configurationElement开始解析映射文件的mapper根标签,标识这个资源已加载,并调用方法bindMapperForNamespace处理命名空间,如果一个映射文件的namespace是一个映射接口类,则要注册这个映射接口类。
无论资源是否已加载,都要处理没有解决的ResultMaps、CacheRefs及Statements
public void parse() {
// 这个资源是否已解析
if (!configuration.isResourceLoaded(resource)) {
// 解析映射文件中根节点mapper的主方法configurationElement
configurationElement(parser.evalNode("/mapper"));
//
configuration.addLoadedResource(resource);
// 该方法处理mapper标签的namespace属性,若namespace属性定义的是一个映射接口类,在这里处理这个接口类
bindMapperForNamespace();
}
// 其他处理,处理没有解决的ResultMaps、CacheRefs及Statements
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
XMLMapperBuilder类的bindMapperForNamespace方法
这个方法用来处理mapper标签的namespace属性,若这个属性是一个映射接口,则将这个类型保存到Configuration对象中。并注册到MapperRegistry中。
private void bindMapperForNamespace() {
// 获得当前命名空间字符串
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) {
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对象中
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
XMLMapperBuilder类的configurationElement方法
private void configurationElement(XNode context)方法比较简单,就是调用相应的方法依次处理映射文件根标签的mapper元素的属性namespace及各个子标签:
cache-ref – 对其他命名空间缓存配置的引用。
cache – 对给定命名空间的缓存配置
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。
sql – 可被其他语句引用的可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句
上面四种SQL语句标签的处理方法是一样的。
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
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);
}
}
后面的博文会依次介绍每个子标签的解析。

浙公网安备 33010602011771号