【Mybatis】ResultMap解析

ResultMap解析

在Mybatis中,resultMap节点定义了结果集和结果对象(JavaBean)之间的映射规则。

本文章主要讲述的是resultMap的解析。

相关基础类

1、ResultMapping:列映射类

ResultMapping对象记录了结果集中的一列与对应JavaBean中一个属性的映射关系;

文章参考:

列映射类ResultMapping

2、ResultMap:结果集映射类ResultMap

ResultMap对应的是结果集中的一个结果集。其基本组成部分中,含有ResultMapping对象。

文章参考:

结果集映射类ResultMap

解析

入口函数

resultMap是解析Mapper的一个环节,其处于mapper.xml文件下。

resultMapElements(context.evalNodes("/mapper/resultMap"));

解析是可以存在多个的,故 context.evalNodes("/mapper/resultMap")返回的是一个List列表。

private void resultMapElements(List<XNode> list)throws Exception{
    for(XNode resultMapNode:list){
        try{
            resultMapElement(resultMapNode);
        }catch(IncompleteElementException e){
            //ignore,it will be retried
        }
    }
}

resultMapElement函数

整个过程都是在resultMapElement这个函数中进行的,相关代码如下:

private ResultMap resultMapElement(XNode resultMapNode) throws Exception{
    return resultMapElement(resultMapNode,Collections,<ResultMapping> emptyList());
}
/**
 * 处理节点,将节点解析成ResultMap对象,下面包含有ResultMapping对象组成的列表
 * @param resultMapNode resultMap节点
 * @param additionalResultMappings 另外的ResultMapping列
 * @return ResultMap对象
 */
private ResultMap resultMapElement(XNode resultMapNode,List<ResultMapping> additionalResultMappings) throws Exception{
    ErrorContext.instance().activity("processing "+resultMapNode.getValueBasedIdentifier());
    //获取Id,默认拼装所有父节点的id或value或property。是一个身份标识,具有唯一性。
    String id = resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());
    //获取type属性,表示结果集将被映射为type指定类型的对象
    String type = resultMapNode.getStringAttribute("type",
                                               resultMapNode.getStringAttribute("ofType",
                                           resultMapNode.getStringAttribute("resultType",
                                         resultMapNode.getStringAttribute("javaType"))));
    //获取extends属性,表示结果集的继承
    String extend = resultMapNode.getStringAttribute("extends");
    //自动映射属性。列名自动映射为属性
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //解析type,获取其类型
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    //记录解析结果
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(additionalResultMappings);
    //处理子节点
    List<XNode> resultChildren = resultMapNode.getChildren();
    for(XNode resultChild : resultChildren){
        //处理constructor节点
        if("constructor".equals(resultChild.getName())){
            //解析构造函数元素,其下的每一个子节点产生一个ResultMapping对象
            processConstructorElement(resultChild,typeClass,resultMappings);
        }else if("discriminator".equals(resultChild.getName())) {//处理discriminator节点
            discriminator = processDiscriminatorElement(resultChild,typeClass,resultMappings);
        }else{//处理其余节点
            List<ResultFlag> flags = new ArrayList<>();
            if("id".equals(resultChild.getName())){
                flags.add(ResultFlag.ID);
            }
            //创建resultMap对象,并添加到resultMappings中
            resultMapping.add(buildResultMappingFromContext(resultChild,typeClass,flags));
        }
    }
    // 创建 ResultMapResolver 对象, 该对象可以生成 ResultMap 对象
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant,id,typeClass,extend, discriminator, resultMappings, autoMapping);
    try{
        return resultMapResolver.resolve();
    }catch (IncompleteElementException e){
        //若无法创建ResultMap对象,则将该结果添加到incompleteResultMaps集合中
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}

详细解析

  • 获取id

    id 对于 resultMap 来说是很重要的, 它是一个身份标识。具有唯一性

    // 获取 ID , 默认值会拼装所有父节点的 id 或 value 或 property。
        String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
    

    这里涉及到 XNode 对象中的两个函数:getStringAttribute()以及getValueBasedIdentifier()

    public String getStringAttribute(String name, String def) {
        String value = attributes.getProperty(name);
        if (value == null) {
            return def;
        } else {
            return value;
        }
    }
    

    该函数是获取 XNode 对象对应 XML 节点的 name 属性值, 如果该属性不存在, 则返回传入的默认值 def。

    而在获取 id 的过程中, 默认值是下面这个函数

    /**
     * 生成元素节点的基础 id
     * @return
     */
    public String getValueBasedIdentifier() {
      StringBuilder builder = new StringBuilder();
      XNode current = this;
      // 当前的节点不为空
      while (current != null) {
        // 如果节点不等于 this, 则在0之前插入 _ 符号, 因为是不断的获取父节点的, 因此是插在前面
        if (current != this) {
          builder.insert(0, "_");
        }
        // 获取 id, id不存在则获取value, value不存在则获取 property。
        String value = current.getStringAttribute("id",
            current.getStringAttribute("value",
                current.getStringAttribute("property", null)));
        // value 非空, 则将.替换为_, 并将value的值加上 []
        if (value != null) {
          value = value.replace('.', '_');
          builder.insert(0, "]");
          builder.insert(0,
              value);
          builder.insert(0, "[");
        }
        // 不管 value 是否存在, 前面都添加上节点的名称
        builder.insert(0, current.getName());
        // 获取父节点
        current = current.getParent();
      }
      return builder.toString();
    }
    

    该函数是生成元素节点的id, 如果是这样子的 XML。

    <employee id="${id_var}">
        <blah something="that"/>
        <first_name>Jimfirst_name>
        <last_name>Smithlast_name>
        <birth_date>
            <year>1970year>
            <month>6month>
            <day>15day>
        birth_date>
        <height units="ft">5.8height>
        <weight units="lbs">200weight>
        <active>trueactive>
    employee>
    

    我们调用

    XNode node = parser.evalNode("/employee/height"); node.getValueBasedIdentifier();
    

    则, 返回值应该是

    employee[${id_var}]_height
    
  • 解析结果集中的类型

    结果集的类型, 对应的是一个 JavaBean 对象。通过反射来获得该类型。

    // 获取type, type 不存在则获取 ofType, ofType 
    // 不存在则获取 resultType, resultType 不存在则获取 javaType
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    // ... ...
    // 获取 type 对应的 Class 对象
    Class<?> typeClass = resolveClass(type);
    

    看源码, 有很多个 def 值, 也就是说, 我们在配置结果集的类型的时候都是有优先级的。但是, 这里有一个奇怪的地方, 我源代码版本(3.5.0-SNAPSHOT)的 的属性, 只有 type, 没有 ofType/resultType/javaType。以下为相应的 DTD 约束:

    <!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)>
    <!ATTLIST resultMap
    id CDATA #REQUIRED
    type CDATA #REQUIRED
    extends CDATA #IMPLIED
    autoMapping (true|false) #IMPLIED>
    

    这里可能是考虑到对以前版本的兼容性。

  • 获取继承结果集和自动映射

    String extend = resultMapNode.getStringAttribute("extends");
       Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    

    这两个属性在配置XML的时候,显得有些可有可无。

  • 解析

    根据类型进行解析,获得resultMappings

    // 创建一个 resultMappings 的链表
       List<ResultMapping> resultMappings = new ArrayList<>();
       // 将从其他地方传入的additionalResultMappings添加到该链表中
       resultMappings.addAll(additionalResultMappings);
       // 获取子节点
       List<XNode> resultChildren = resultMapNode.getChildren();
       // 遍历解析子节点
       for (XNode resultChild : resultChildren) {
         if ("constructor".equals(resultChild.getName())) {
           // 解析构造函数元素,其下的没每一个子节点都会生产一个 ResultMapping 对象
           processConstructorElement(resultChild, typeClass, resultMappings);
         } else if ("discriminator".equals(resultChild.getName())) {
           // 解析 discriminator 节点
           discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
         } else {
           // 解析其余的节点
           List<ResultFlag> flags = new ArrayList<>();
           if ("id".equals(resultChild.getName())) {
             flags.add(ResultFlag.ID);
           }
           resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
         }
       }
    

    除了discriminator节点,其他节点最后都会回到buildResultMappingFromContext方法,该方法为创建ResultMapping对象。

    /**
       * 获取一行, 如result等, 取得他们所有的属性, 通过这些属性建立 `ResultMapping` 对象
       * @param context 对于节点本身
       * @param resultType resultMap 的结果类型
       * @param flags flag 属性, 对应 ResultFlag 枚举中的属性。 一般情况下为空
       * @return 返回 ResultMapping
       * @throws Exception
       */
      private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
        String property;
        // 获取节点的属性, 如果节点是构造函数(只有name属性, 没有property),
        // 则获取的是 name, 否则获取 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");
        // 获取嵌套的结果集
        String nestedResultMap = context.getStringAttribute("resultMap",
            processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
        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);
        @SuppressWarnings("unchecked")
        Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        // 创建resultMapping对象
        return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
      }
    

    对于discriminator,则处理该元素,并创建鉴别器。

    /**
       * 处理鉴别器
       * @param context 节点
       * @param resultType 结果类型
       * @param resultMappings 列结果集合
       * @return 鉴别器
       * @throws Exception
       */
      private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
        String column = context.getStringAttribute("column");
        String javaType = context.getStringAttribute("javaType");
        String jdbcType = context.getStringAttribute("jdbcType");
        String typeHandler = context.getStringAttribute("typeHandler");
        // 先获取各个属性
        // 取得 javaType 对应的类型
        Class<?> javaTypeClass = resolveClass(javaType);
        // 取得 typeHandler 对应的类型
        @SuppressWarnings("unchecked")
        Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
        // 取得 jdbcType 对应的类型
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        // 创建 discriminatorMap, 并遍历子节点, 以 value->resultMap 的方式放入discriminatorMap中
        Map<String, String> discriminatorMap = new HashMap<>();
        for (XNode caseChild : context.getChildren()) {
          String value = caseChild.getStringAttribute("value");
          String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
          discriminatorMap.put(value, resultMap);
        }
        // 创建鉴别器
        return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
      }
    

    在鉴别器内部中,也是含有ResultMapping的:

    public class Discriminator {
      private ResultMapping resultMapping;
      private Map<String, String> discriminatorMap;
      ......
    }
    
  • 创建ResultMap对象

    解析各个属性和子节点完毕之后,创建ResultMapResolver对象,通过对象可以生成ResultMap。

    /**
       * 创建并添加 ResultMap 到 Configuration 对象中
       * @param id id, 配置了 id 可以提高效率
       * @param type 类型
       * @param extend 继承
       * @param discriminator 鉴别器
       * @param resultMappings 列集
       * @param autoMapping 是否自动映射
       * @return 返回创建的 ResultMap 对象
       */
      public ResultMap addResultMap(
          String id,
          Class<?> type,
          String extend,
          Discriminator discriminator,
          List<ResultMapping> resultMappings,
          Boolean autoMapping) {
        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 + "'");
          }
          // 从 configuration 中获取继承的结果集
          ResultMap resultMap = configuration.getResultMap(extend);
          // 获取所集成结果集的所有 ResultMapping 集合
          List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
          // 移除需要覆盖的 ResultMapping 集合
          extendedResultMappings.removeAll(resultMappings);
          // 如果该 resultMap 中定义了构造节点, 则移除其父节点的构造器
          boolean declaresConstructor = false;
          for (ResultMapping resultMapping : resultMappings) {
            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
              declaresConstructor = true;
              break;
            }
          }
          if (declaresConstructor) {
            Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
            while (extendedResultMappingsIter.hasNext()) {
              if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                extendedResultMappingsIter.remove();
              }
            }
          }
          // 添加需要被继承的 ResultMapping 集合
          resultMappings.addAll(extendedResultMappings);
        }
        // 通过建造者模式创建 ResultMap 对象
        ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
            .discriminator(discriminator)
            .build();
        // 添加到 Configuration 对象中
        configuration.addResultMap(resultMap);
        return resultMap;
      }
    

Github示例代码

https://github.com/homejim/mybatis-cn

你可能感兴趣的:(Java,SpringBoot)