Mapper解析
从上一篇文章我们分析到,Mapper的解析主要分为两类:
- 解析Mapper接口上的注解
- 解析xml配置文件
解析xml配置文件
如果想要通过解析xml配置文件来注册Mapper的话,有以下两个方法
- 使用中的resource来指定mapper配置文件在当前工程中的路径
- 使用中的url来指定mapper配置文件在网络上的位置
不管使用上述哪种方式,最终都会产生一个输入流,然后将这个输入流交给XMLMapperBuilder进行处理
下面主要看下XMLMapperBuilder的parse方法
public void parse() {
// 避免重复加载
if (!configuration.isResourceLoaded(resource)) {
// 加载mapper下的各个子标签,比如select,resultmap等
configurationElement(parser.evalNode("/mapper"));
// 标记当前资源已经加载完毕
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// 处理解析失败的ResultMap
parsePendingResultMaps();
// 处理解析失败的CacheRef
parsePendingCacheRefs();
// 处理解析失败的sql
parsePendingStatements();
}
configurationElement
下面主要看下configurationElement这个方法,这个方法主要是用来解析Mapper xml配置文件中的各个节点的
可以看到针对不同的子标签,分别使用不同的方法来进行处理,下面分别看下每个方法是怎么处理对应的子标签的
private void configurationElement(XNode context) {
try {
// 解析mapper标签中的namespace
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
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);
}
}
cacheRefElement
在Mapper xml配置文件中,可以通过来使用指定命名空间的缓存作为自己使用的缓存
<cache-ref namespace="mapper.StudentMapper"/>
private void cacheRefElement(XNode context) {
if (context != null) {
// 将当前这个Mapper使用的缓存所在的命名空间关联关系放到全局configuration中
// 这里就是一个简单的map的put操作
// 使用的map是 protected final Map<String, String> cacheRefMap = new HashMap<>();
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
// 创建一个缓存引用解析对象,这个对象会去全局配置对象中寻找当前引用的缓存
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
// 解析引用的缓存
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
// 如果没有找到引用的缓存,那么会将这个缓存缺失记录在全局配置对象中
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
下面看下CacheRefResolver的resolveCacheRef
public Cache resolveCacheRef() {
return assistant.useCacheRef(cacheRefNamespace);
}
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
// 从全局配置文件中根据当前引用缓存所在的命名空间来获取缓存对象
Cache cache = configuration.getCache(namespace);
// 如果没有获取到,可能是因为没有解析到缓存所在的命名空间,此时会抛出异常
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
从上面的过程我们了解到,当解析Mapper配置文件时,是按照在mybatis文件中声明的顺序来解析的,这就可能会导致前一个Mapper依赖的缓存在后一个Mapper中声明,当解析前一个Mapper的时候,就会找不到引用的cache对象,然后抛出异常。mybatis会捕获这个异常,然后将这个引用缺失存放在全局配置对象中。那么什么时候会解决这个依赖缺失问题呢
这里要回过头看下XMLMapperBuilder的parse方法
public void parse() {
// 避免重复加载
if (!configuration.isResourceLoaded(resource)) {
// 加载mapper下的各个子标签,比如select,resultmap等
configurationElement(parser.evalNode("/mapper"));
// 标记当前资源已经加载完毕
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
接着看下parsePendingCacheRefs
private void parsePendingCacheRefs() {
// 当一个Mapper配置文件解析完毕后,会遍历当前全局配置对象中的incompletetCacheRefs,也就是引用的Cache缺失
// 针对每个cache缺失,会尝试重新解析,判断当前全局配置对象中是否存在引用的cahce对象,如果存在,就会将这个应用cache缺失删除掉
Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
synchronized (incompleteCacheRefs) {
Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
while (iter.hasNext()) {
try {
iter.next().resolveCacheRef();
iter.remove();
} catch (IncompleteElementException e) {
// Cache ref is still missing a resource...
}
}
}
}
cacheElement
这里比较简单,就是解析标签上的属性,将相应的属性设置到Cache对象上
private void cacheElement(XNode context) {
if (context != null) {
// 获取<cache>上的type属性,代表使用的缓存的类型,默认是PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
// 从类型别名中解析type对应的类名称
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 获取<cache>上的eviction,代表缓存清退策略,也就是缓存的装饰类,默认是LRU
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// flush间隔
Long flushInterval = context.getLongAttribute("flushInterval");
// 缓存大小
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
// 使用上述属性创建一个Cache对象,并且添加到全局对象中
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
parameterMapElement
mybatis已经不建议使用ParameterMap了,所以这里就不介绍了
resultMapElements
一个ResultMap包含了多个ResultMapping对象,每个ResultMapping对象代表数据库中的一列和java bean中一个属性的对应关系
通过使用ResultMap,就可以将数据库中查询得到的记录转换为我们期望的java bean
下面来看下源码
private void resultMapElements(List<XNode> list) {
for (XNode resultMapNode : list) {
try {
// 遍历每个<resultMap>
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 从标签上的四个属性来解析type,这里的type就是每条查询结果转换后的类
// 优先级为type > ofType > resultType > javaType
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// resolveClass方法首先会将type看做别名,从typeAliasRegistry中找到类的全路径
// 如果没有找到,那么将type看做全路径别名
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
// 用来存放解析后的ResultMapping
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
// 解析子标签 比如<constructor> <discriminator> <id> <result>
for (XNode resultChild : resultChildren) {
// 解析<constructor>标签
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
// 解析<discriminator>标签
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 解析<id> <result>标签
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
String id = resultMapNode.getStringAttribute("id",
resultMapNode