【Mybatis】【配置文件解析】【四】Mybatis源码解析-mappers的解析二(resultMap)
1 前言
上节我们的 mapper 解析了关于二级缓存 cache、cache-ref的解析,这节我们解析 resultMap,建议你们边看的时候边调试,resultMap 的东西很多,首先你要知道 resultMap 标签节点中都能写什么,知道有什么,再去看每一步具体是要解析什么。
关于这节的文档官网也有讲解:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
2 resuleMap简介
在进行解析之前,我们先大概看看resultMap中大概有什么,resultMap
元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets
数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap
能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
首先我们来看下 resultMap 节点的dtd约束:
<!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)> <!ATTLIST resultMap id CDATA #REQUIRED type CDATA #REQUIRED extends CDATA #IMPLIED autoMapping (true|false) #IMPLIED >
constructor
- 用于在实例化类时,注入结果到构造方法中idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能result
– 注入到字段或 JavaBean 属性的普通结果association
– 一个复杂类型的关联;许多结果将包装成这种类型- 嵌套结果映射 – 关联可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
collection
– 一个复杂类型的集合- 嵌套结果映射 – 集合可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
discriminator
– 使用结果值来决定使用哪个resultMap
case
– 基于某些值的结果映射- 嵌套结果映射 –
case
也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
- 嵌套结果映射 –
属性 | 描述 |
---|---|
id |
当前命名空间中的一个唯一标识,用于标识一个结果映射。 |
type |
类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。 |
autoMapping |
如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。 |
最佳实践 最好逐步建立结果映射。单元测试可以在这个过程中起到很大帮助。 如果你尝试一次性创建像上面示例那么巨大的结果映射,不仅容易出错,难度也会直线上升。 所以,从最简单的形态开始,逐步迭代。而且别忘了单元测试! 有时候,框架的行为像是一个黑盒子(无论是否开源)。因此,为了确保实现的行为与你的期望相一致,最好编写单元测试。 并且单元测试在提交 bug 时也能起到很大的作用。
3 解析 <resultMap> 节点
3.1 方法通读
接下来我们就进入源码解析过程:
private void resultMapElements(List<XNode> list) { // 遍历 <resultMap> 节点列表 for (XNode resultMapNode : list) { try { // 逐个解析 resultMap 节点 resultMapElement(resultMapNode); } catch (IncompleteElementException e) { // ignore, it will be retried } } } // 对每个 resultMap 进行解析 private ResultMap resultMapElement(XNode resultMapNode) { // 真正开始的地方 return resultMapElement(resultMapNode, Collections.emptyList(), null); } /** * 看参数的来源 * @param resultMapNode 就是我们 resultMap 节点的内容 * @param additionalResultMappings 空集合 * @param enclosingType null * @return */ private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) { // 这个暂时不知道起什么作用 ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); /** * 获取 type 属性 * 没有的话依次取 ofType、resultType、javaType */ String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); /** * 根据 type 得到 class * type值 可能是全类名也有可能是 别名 所以这里处理一下 */ Class<?> typeClass = resolveClass(type); /** * 因为 resultMap 里边会嵌套到 association case 所所以这里处理这两种情况 * 如果类型为空,则继承父类的返回类型,主要为 association、case 节点使用 */ if (typeClass == null) { typeClass = inheritEnclosingType(resultMapNode, enclosingType); } Discriminator discriminator = null; List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings); // 子节点 List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { // 解析 constructor 节点,并生成相应的 ResultMapping if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { // 解析 discriminator 节点 discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { // 剩下的节点 List<ResultFlag> flags = new ArrayList<>(); // 如果是 id 节点 添加 ID 到 flags 集合中 if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } // 解析 id 和 property 节点,并生成相应的 ResultMapping resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } // 获取 id 、 extends 、autoMapping 属性 String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); // 构建 ResultMapResolver 解析器 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { // 进行解析 return resultMapResolver.resolve(); } catch (IncompleteElementException e) { /** * 异常的话 放进 configuration 的不完整缓存中 * protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>(); */ configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
上面的代码比较多,看起来有点复杂,这里总结一下:
- 获取 <resultMap> 节点的各种属性;
- 遍历 <resultMap> 的子节点,并根据子节点名称执行相应的解析逻辑;
- 构建 ResultMap 对象;
- 若构建过程中发生异常,则将 resultMapResolver 添加到 incompleteResultMaps 集合中。
那么我们开始逐一对每个节点进行剖析。
3.2 解析 <id> 和 <result> 节点
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) { String property; /** * 根据节点类型获取 name 或 property 属性 * 我们的 resultMap 下的 直接子节点id、result 都是走的 else逻辑 获取的 property 值 */ if (flags.contains(ResultFlag.CONSTRUCTOR)) { property = context.getStringAttribute("name"); } else { property = context.getStringAttribute("property"); } /** 其它属性值 */ String column = context.getStringAttribute("column"); String javaType = context.getStringAttribute("javaType"); String jdbcType = context.getStringAttribute("jdbcType"); String nestedSelect = context.getStringAttribute("select"); /** * 解析 resultMap 属性,该属性出现在 <association> 、 <collection> <case> 即可以有 resultMap 属性的节点中。 * 若这节点不包含 resultMap 属性,则调用 processNestedResultMappings 方法 * 解析嵌套 resultMap。 * 比如对于 association 这样的配置就会继续递归解析 * <association property="author" javaType="Author"> * <id property="id" column="author_id"/> * <result property="username" column="author_username"/> * </association> */ String nestedResultMap = context.getStringAttribute("resultMap", () -> processNestedResultMappings(context, Collections.emptyList(), resultType)); String notNullColumn = context.getStringAttribute("notNullColumn"); String columnPrefix = context.getStringAttribute("columnPrefix"); String typeHandler = context.getStringAttribute("typeHandler"); String resultSet = context.getStringAttribute("resultSet"); String foreignColumn = context.getStringAttribute("foreignColumn"); boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager") // 解析 javaType、typeHandler 的类型以及枚举类型 JdbcType Class<?> javaTypeClass = resolveClass(javaType); Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); // 构建 ResultMapping 对象 return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedRes } private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) { if (Arrays.asList("association", "collection", "case").contains(context.getName()) && context.getStringAttribute("select") == null) { /** * 会对集合类型的属性 进行属性的 setter 检查 */ validateCollection(context, enclosingType); ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType); return resultMap.getId(); } return null; } protected void validateCollection(XNode context, Class<?> enclosingType) { if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null && context.getStringAttribute("javaType") == null) { MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory()); String property = context.getStringAttribute("property"); if (!metaResultType.hasSetter(property)) { throw new BuilderException( "Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'."); } } }
接下来我们看下 ResultMapping 的构建过程:
public ResultMapping buildResultMapping( Class<?> resultType, String property, String column, Class<?> javaType, JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix, Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn, boolean lazy) { /** * resultType 返回值的类型 * property 当前的属性 * javaType 属性对应的java类型 * id、result 节点的 resultType 其实是 resultMap 上的 type 值 你自己调试的时候可以发现 * 当你的 javaType 为空时, 就会根据你的 resultType 类型和属性 property 去获取你的 setterType * 假如没获取到的话 javaTypeClass 默认为 Object.class */ Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType); // 解析 TypeHandler TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); /* * 解析 column = {property1=column1, property2=column2} 的情况, * 这里会将 column 拆分成多个 ResultMapping */ List<ResultMapping> composites; if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) composites = Collections.emptyList(); } else { composites = parseCompositeColumnName(column); } // 通过建造器构建 ResultMapping return new ResultMapping.Builder(configuration, property, column, javaTypeClass) .jdbcType(jdbcType) .nestedQueryId(applyCurrentNamespace(nestedSelect, true)) .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true)) .resultSet(resultSet) .typeHandler(typeHandlerInstance) .flags(flags == null ? new ArrayList<>() : flags) .composites(composites) .notNullColumns(parseMultipleColumnNames(notNullColumn)) .columnPrefix(columnPrefix) .foreignColumn(foreignColumn) .lazy(lazy) .build(); }
// ResultMapping.Builder public ResultMapping build() { // lock down collections 不可修改 哟 又学到了 resultMapping.flags = Collections.unmodifiableList(resultMapping.flags); resultMapping.composites = Collections.unmodifiableList(resultMapping.composites); // 从 TypeHandlerRegistry 中获取相应 TypeHandler resolveTypeHandler(); validate(); return resultMapping; }
3.3 解析 <constructor> 节点
官网的例子以及说明我就不搬过来了哈,我们直接分析 constructor 节点的解析过程。
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) { // 获取子节点列表 List<XNode> argChildren = resultChild.getChildren(); for (XNode argChild : argChildren) { List<ResultFlag> flags = new ArrayList<>(); // 向 flags 中添加 CONSTRUCTOR 标志 flags.add(ResultFlag.CONSTRUCTOR); if ("idArg".equals(argChild.getName())) { // 向 flags 中添加 ID 标志 flags.add(ResultFlag.ID); } // 构建 ResultMapping 这个跟上一步的构建是同一个方法 resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags)); } }
构造器的解析其实东西不多,因为这个标签节点以及本身节点的属性不多,所以这里就不细讲了哈。
3.4 ResultMap 对象构建过程分析
通过前面的分析,我们可知 <id>,<result> 等节点最终都被解析成了 ResultMapping。在得到这些 ResultMapping 后,紧接着要做的事情是构建 ResultMap。
// 构建 ResultMapResolver 解析器 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { // 进行解析 return resultMapResolver.resolve(); } catch (IncompleteElementException e) { /** * 异常的话 放进 configuration 的不完整缓存中 * protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>(); */ configuration.addIncompleteResultMap(resultMapResolver); throw e; }
其实还是调用我们的 MapperBuilderAssistant 里的 addResultMap 方法。
public ResultMap resolve() { return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping); }
那么我们看下方法里具体都做了什么东西:
public ResultMap addResultMap( String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) { // 为 ResultMap 的 id 和 extend 属性值拼接命名空间 id = applyCurrentNamespace(id, false); extend = applyCurrentNamespace(extend, true); if (extend != null) { if (!configuration.hasResultMap(extend)) { throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'"); } ResultMap resultMap = configuration.getResultMap(extend); List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings()); // 为拓展 ResultMappings 取出重复项 extendedResultMappings.removeAll(resultMappings); // Remove parent constructor if this resultMap declares a constructor. boolean declaresConstructor = false; for (ResultMapping resultMapping : resultMappings) { if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { declaresConstructor = true; break; } } /* * 如果当前 <resultMap> 节点中包含 <constructor> 子节点, * 则将拓展 ResultMapping 集合中的包含 CONSTRUCTOR 标志的元素移除 */ if (declaresConstructor) { extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)); } // 将扩展 resultMappings 集合合并到当前 resultMappings 集合中 resultMappings.addAll(extendedResultMappings); } // 构建 ResultMap ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build(); configuration.addResultMap(resultMap); return resultMap; }
上面的方法主要用于处理 resultMap 节点的 extend 属性,extend 不为空的话,这里将当前 resultMappings 集合和扩展 resultMappings 集合合二为一。随后,通过建造模式构建 ResultMap 实例。过程如下:
public ResultMap build() { if (resultMap.id == null) { throw new IllegalArgumentException("ResultMaps must have an id"); } // 5个容器 resultMap.mappedColumns = new HashSet<>(); resultMap.mappedProperties = new HashSet<>(); resultMap.idResultMappings = new ArrayList<>(); resultMap.constructorResultMappings = new ArrayList<>(); resultMap.propertyResultMappings = new ArrayList<>(); final List<String> constructorArgNames = new ArrayList<>(); for (ResultMapping resultMapping : resultMap.resultMappings) { /* * 检测 <association> 或 <collection> 节点 * 是否包含 select 和 resultMap 属性 */ resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null; resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != nu final String column = resultMapping.getColumn(); if (column != null) { // 将 column 转换成大写,并添加到 mappedColumns 集合中 resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH)); } else if (resultMapping.isCompositeResult()) { for (ResultMapping compositeResultMapping : resultMapping.getComposites()) { final String compositeColumn = compositeResultMapping.getColumn(); if (compositeColumn != null) { resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH)); } } } // 添加属性 property 到 mappedProperties 集合中 final String property = resultMapping.getProperty(); if (property != null) { resultMap.mappedProperties.add(property); } // 检测当前 resultMapping 是否包含 CONSTRUCTOR 标志 if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { // 添加 resultMapping 到 constructorResultMappings 中 resultMap.constructorResultMappings.add(resultMapping); // 添加属性(constructor 节点的 name 属性)到 constructorArgNames 中 if (resultMapping.getProperty() != null) { constructorArgNames.add(resultMapping.getProperty()); } } else { // 添加 resultMapping 到 propertyResultMappings 中 resultMap.propertyResultMappings.add(resultMapping); } if (resultMapping.getFlags().contains(ResultFlag.ID)) { // 添加 resultMapping 到 idResultMappings 中 resultMap.idResultMappings.add(resultMapping); } } if (resultMap.idResultMappings.isEmpty()) { resultMap.idResultMappings.addAll(resultMap.resultMappings); } if (!constructorArgNames.isEmpty()) { // 获取构造方法参数列表 final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames); if (actualArgNames == null) { throw new BuilderException("Error in result map '" + resultMap.id + "'. Failed to find a constructor in '" + resultMap.getType().getName() + "' by arg names " + constructorArgNames + ". There might be more info in debug log."); } // 对 constructorResultMappings 按照构造方法参数列表的顺序进行排序 resultMap.constructorResultMappings.sort((o1, o2) -> { int paramIdx1 = actualArgNames.indexOf(o1.getProperty()); int paramIdx2 = actualArgNames.indexOf(o2.getProperty()); return paramIdx1 - paramIdx2; }); } // lock down collections 变为不可修改集合 resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings); resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings); resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings); resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings); resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns); return resultMap; }
声明了5类容器,按情况分别将属性或者实例信息进行存放,并设置为不可变,那么5个容器分别存放的什么呢:
集合名称 |
用途 |
---|---|
mappedColumns |
用于存储 <id>、<result>、<idArg>、<arg> 节点 column 属性 |
mappedProperties |
用于存储 <id> 和 <result> 节点的 property 属性,或 <idArgs> 和 <arg> 节点的 name 属性 |
idResultMappings |
用于存储 <id> 和 <idArg> 节点对应的 ResultMapping 对象 |
propertyResultMappings |
用于存储 <id> 和 <result> 节点对应的 ResultMapping 对象 |
constructorResultMappings |
用于存储 <idArgs> 和 <arg> 节点对应的 ResultMapping 对象 |
来个例子我们调试看下这5个集合哈:
<mapper namespace="org.apache.ibatis.test.DemoMapper"> <resultMap id="myResultMap" type="org.apache.ibatis.test.DemoPo"> <constructor> <arg column="id" name="id"/> <arg column="name" name="name"/> </constructor> <id column="id" property="id"/> <result column="name" property="name"/> <association property="sun" javaType="org.apache.ibatis.test.SunPo"> <id property="id" column="author_id"/> <result property="title" column="title"/> </association> </resultMap> </mapper>
4 小结
好关于 resultMap 节点的东西还是蛮多的,就是解析,嵌套解析递归调用,收集汇总的一个过程,解析完会对每个集合进行不可变,防止运行修改,大家边看的同时,要边调试奥,有理解不对的地方欢迎指正哈。