接下来,我们对照上面的配置分析 cache-ref 的解析过程。如下:
private void cacheRefElement(XNode context) { if (context != null) { configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); // 创建 CacheRefResolver 实例 CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); try { // 解析参照缓存 cacheRefResolver.resolveCacheRef(); } catch (IncompleteElementException e) { /* * 这里对 IncompleteElementException 异常进行捕捉,并将 cacheRefResolver * 存入到 Configuration 的 incompleteCacheRefs 集合中 */ configuration.addIncompleteCacheRef(cacheRefResolver); } } }如上所示,<cache-ref> 节点的解析逻辑封装在了 CacheRefResolver 的 resolveCacheRef 方法中。下面,我们一起看一下这个方法的逻辑。
// -☆- CacheRefResolver public Cache resolveCacheRef() { // 调用 builderAssistant 的 useNewCache(namespace) 方法 return assistant.useCacheRef(cacheRefNamespace); } // -☆- MapperBuilderAssistant public Cache useCacheRef(String namespace) { if (namespace == null) { throw new BuilderException("cache-ref element requires a namespace attribute."); } try { unresolvedCacheRef = true; // 根据命名空间从全局配置对象(Configuration)中查找相应的缓存实例 Cache cache = configuration.getCache(namespace); /* * 若未查找到缓存实例,此处抛出异常。这里存在两种情况导致未查找到 cache 实例, * 分别如下: * 1.使用者在 <cache-ref> 中配置了一个不存在的命名空间, * 导致无法找到 cache 实例 * 2.使用者所引用的缓存实例还未创建 */ if (cache == null) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found."); } // 设置 cache 为当前使用缓存 currentCache = cache; unresolvedCacheRef = false; return cache; } catch (IllegalArgumentException e) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e); } }以上是 cache-ref 的解析过程,逻辑并不复杂。不过这里要注意 cache 为空的情况,我在代码中已经注释了可能导致 cache 为空的两种情况。第一种情况比较好理解,第二种情况稍微复杂点,但是也不难理解。我会在 2.3 节进行解释说明,这里先不说。
到此,关于 <cache-ref> 节点的解析过程就分析完了。本节的内容不是很难理解,就不多说了。
2.1.3 解析 <resultMap> 节点resultMap 是 MyBatis 框架中常用的特性,主要用于映射结果。resultMap 是 MyBatis 提供的一个强力武器,这一点官方文档中有所描述,这里引用一下。
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来, 并在一些情形下允许你做一些 JDBC 不支持的事情。 实际上,在对复杂语句进行联合映射的时候,它很可能可以代替数千行的同等功能的代码。 ResultMap 的设计思想是,简单的语句不需要明确的结果映射,而复杂一点的语句只需要描述它们的关系就行了。
如上描述,resultMap 元素是 MyBatis 中最重要最强大的元素,它可以把大家从 JDBC ResultSets 数据提取的工作中解放出来。通过 resultMap 和自动映射,可以让 MyBatis 帮助我们完成 ResultSet → Object 的映射,这将会大大提高了开发效率。关于 resultMap 的用法,我相信大家都比较熟悉了,所以这里我就不介绍了。当然,如果大家不熟悉也没关系,MyBatis 的官方文档上对此进行了详细的介绍,大家不妨去看看。
好了,其他的就不多说了,下面开始分析 resultMap 配置的解析过程。
// -☆- XMLMapperBuilder private void resultMapElements(List<XNode> list) throws Exception { // 遍历 <resultMap> 节点列表 for (XNode resultMapNode : list) { try { // 解析 resultMap 节点 resultMapElement(resultMapNode); } catch (IncompleteElementException e) { // ignore, it will be retried } } } private ResultMap resultMapElement(XNode resultMapNode) throws Exception { // 调用重载方法 return resultMapElement(resultMapNode, Collections.<ResultMapping>emptyList()); } private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); // 获取 id 和 type 属性 String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); // 获取 extends 和 autoMapping String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); // 解析 type 属性对应的类型 Class<?> typeClass = resolveClass(type); Discriminator discriminator = null; List<ResultMapping> resultMappings = new ArrayList<ResultMapping>(); resultMappings.addAll(additionalResultMappings); // 获取并遍历 <resultMap> 的子节点列表 List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { if ("constructor".equals(resultChild.getName())) { // 解析 constructor 节点,并生成相应的 ResultMapping processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { // 解析 discriminator 节点 discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList<ResultFlag>(); if ("id".equals(resultChild.getName())) { // 添加 ID 到 flags 集合中 flags.add(ResultFlag.ID); } // 解析 id 和 property 节点,并生成相应的 ResultMapping resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { // 根据前面获取到的信息构建 ResultMap 对象 return resultMapResolver.resolve(); } catch (IncompleteElementException e) { /* * 如果发生 IncompleteElementException 异常, * 这里将 resultMapResolver 添加到 incompleteResultMaps 集合中 */ configuration.addIncompleteResultMap(resultMapResolver); throw e; } }上面的代码比较多,看起来有点复杂,这里总结一下:
获取 <resultMap> 节点的各种属性
遍历 <resultMap> 的子节点,并根据子节点名称执行相应的解析逻辑
构建 ResultMap 对象
若构建过程中发生异常,则将 resultMapResolver 添加到 incompleteResultMaps 集合中