【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;
  }
}

上面的代码比较多,看起来有点复杂,这里总结一下:

  1. 获取 <resultMap> 节点的各种属性;
  2. 遍历 <resultMap> 的子节点,并根据子节点名称执行相应的解析逻辑;
  3. 构建 ResultMap 对象;
  4. 若构建过程中发生异常,则将 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 节点的东西还是蛮多的,就是解析,嵌套解析递归调用,收集汇总的一个过程,解析完会对每个集合进行不可变,防止运行修改,大家边看的同时,要边调试奥,有理解不对的地方欢迎指正哈。

posted @ 2023-02-26 23:06  酷酷-  阅读(141)  评论(0编辑  收藏  举报