MyBatis 源码解析(六)MyBatis如何解析配置 ?(六)

XMLMapperBuilder###parameterMapElement()

//代码比较长了,因为parameterMap 涉及到比较多的东西  
//    <resultMap id="userMap" type="com.test.demo.model.SysUser">
//        <id property="id" column="id"/>
//        <result property="userPassword" column="user_password"/>
//        <result property="userName" column="user_name"/>
//        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
//    </resultMap>
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
    //首先记录一下跟踪日志,有关ErrorContext后面会详细明说
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //这里可以看到用了一连串的嵌套,其实就是设置默认值而已
    //简单解释这里的操作就是:首先获取`type`的值
    //如果`type`为空就取`ofType`的值
    //如果`ofType`为空就取`resultType`的值
    //如果`resultType`为空就取`javaType`的值
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    //解析这个类
    Class<?> typeClass = resolveClass(type);
    //如果type为null
    //一般resolveClass只有在type为null的时候才会返回null
    if (typeClass == null) {
      //这里暂时没看懂,因为解析`ResultMap`传入的enclosingType为null  
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(additionalResultMappings);
    //解析子节点
    List<XNode> resultChildren = resultMapNode.getChildren();

    for (XNode resultChild : resultChildren) {
      //单独处理构造函数节点
      if ("constructor".equals(resultChild.getName())) {
        //处理构造函数节点  
        processConstructorElement(resultChild, typeClass, resultMappings);
         //单独处理鉴定器节点 
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        //如果子节点为<id>,则将其保存起来,后续用来充当equals的作用 
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        //处理其他的子节点  
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    //获取此节点的id
    String id = resultMapNode.getStringAttribute("id",
            //不存在则自动生成一个唯一id                            
            resultMapNode.getValueBasedIdentifier());
    //获取继承节点信息
    String extend = resultMapNode.getStringAttribute("extends");
    //是否自动映射
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //创建解析类
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      //进行解析  
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

说实话,这段代码是在比较难理解,就光前面的嵌套获取type \ ofType \ jdbcType 等找了十万遍都没找到为什么。。甚至想到了是不是兼容老版本的问题。。可以看提交记录这些代码9年前都有了。。。

太难了

后来配合官方文档看,也能大概明白:

<resultMap id="detailedBlogResultMap" type="Blog">
    <constructor>
      <idArg column="blog_id" javaType="int"/>
    </constructor>
    <result property="title" column="blog_title"/>
    <association property="author" javaType="Author">
      <id property="id" column="author_id"/>
      <result property="username" column="author_username"/>
      <result property="password" column="author_password"/>
      <result property="email" column="author_email"/>
      <result property="bio" column="author_bio"/>
      <result property="favouriteSection" column="author_favourite_section"/>
    </association>
    <collection property="posts" ofType="Post">
      <id property="id" column="post_id"/>
      <result property="subject" column="post_subject"/>
      <association property="author" javaType="Author"/>
      <collection property="comments" ofType="Comment">
        <id property="id" column="comment_id"/>
      </collection>
      <collection property="tags" ofType="Tag" >
        <id property="id" column="tag_id"/>
      </collection>
      <discriminator javaType="int" column="draft">
        <case value="1" resultType="DraftPost"/>
      </discriminator>
    </collection>
  </resultMap>
  

可以发现resultMap中可以嵌套<constructor>或者<collection>

<collection>中又可以嵌套其他的,比如<association> \ <collection>

那如果要完整的解析这些嵌套的东西,最好的办法就是递归,

这就是为什么前面会有3中type的获取,因为这个方法不仅仅是在解析<resultMap>,还会被递归调用来解析<association> \ <collection> 等等。

先跳过其他的代码,我们先看看解析其他子节点

XMLMapperBuilder###buildResultMappingFromContext()

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String 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",
//处理嵌套的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"));
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    //将这些属性通过解析助手进行解析
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

这里最重要的便是处理嵌套的resultMap

一般来说,能被嵌套的元素有:

XMLMapperBuilder###processNestedResultMappings()

  private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) throws Exception {
    if ("association".equals(context.getName())
      || "collection".equals(context.getName())
      || "case".equals(context.getName())) {
      //先不解析动态SQL  
      if (context.getStringAttribute("select") == null) {
        //验证collection节点是否包含必须要元素
        //必须包含resultMap 和 javaType其中一个  
        validateCollection(context, enclosingType);
        //调用最开始的方法,进行递归解析  
        ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
        return resultMap.getId();
      }
    }
    return null;
  }

这里就能能看出,已经开始递归调用了

接下来,我们以<association> / <collection>为主体元素,再次分析一遍resultMapElement的源代码

XMLMapperBuilder###parameterMapElement()

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //对于resultMap 获取 type
    //对于collection 获取 ofType 或 javaType
    //对于association 获取 javaType
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    //加载此type,可以指定别名
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      //这里enclosingType一般都是父节点的type 
      //比如<resultMap type="test">
      //       <collection/>
      //那么enclosingType便是test  
      //如果有些节点没有配置type,允许的情况下,可以直接使用父节点的type
      //例如:
      //    <discriminator javaType="int" column="draft">
      //        <case value="1" resultType="DraftPost"/>
      //    </discriminator>
      // case节点中就没有配置type  
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    //添加已经解析过的resultMap
    resultMappings.addAll(additionalResultMappings);
    //继续解析子节点
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      //处理构造方法  
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      }
      //处理鉴定器  
       else if ("discriminator".equals(resultChild.getName())) {
        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));
      }
    }
    //获取id
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //构建resultMap
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      //最后的解析  
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

接下来看其他细节:

当子节点解析完了以后,会将子节点添加到resultMapping中,然后再解析最大的resultMap

MapperBuilderAssiant###addResultMap()

//resultMapResolver.resolve();内部调用的此方法
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 + "'");
      }
      //通过继承找到对应的resultMap  
      ResultMap resultMap = configuration.getResultMap(extend);
      //获取这个resultMap id 下对应的所有resultMap (包括嵌套resultMap)
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      //去除重复resultMap
      extendedResultMappings.removeAll(resultMappings);
      // Remove parent constructor if this resultMap declares a constructor.
      boolean declaresConstructor = false;
      //检查是否需要使用带参构造方法构造resultMap
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      //如果需要使用构造方法创键`resultMap type`,那么将此需要使用构造方法的元素从继承元素中删除  
      if (declaresConstructor) {
        extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
      }
      resultMappings.addAll(extendedResultMappings);
    }
    //构造resultMap
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    configuration.addResultMap(resultMap);
    return resultMap;
  }

这几段代码看着实在累,第一是比较复杂,第二个就是本来想要实现的功能也比较复杂。

这里可以看见MyBatis仅仅是将嵌套的resultMap分解为几个reusltMap,然后放入list中,并且这里解析配置就真正的只是解析配置,没有做任何多余的事、

同时可以看出来,MyBatis的模块划分是非常好的,

XMLConfigBuilder->XMLMapperBuilder->MapperBuilderAssistant

XMLMapperBuilder只用负责读取配置文件,而将配置文件生成对像则交给MapperBuilderAssistant


看上面的代码,resultMap也只解析了静态部分能够解析的地方,而需要动态生成的则直接原封不动的放入了Configuration类中

发表评论