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 {
    //首先記錄一下跟蹤日志,有關(guān)ErrorContext后面會詳細(xì)明說
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //這里可以看到用了一連串的嵌套,其實就是設(shè)置默認(rèn)值而已
    //簡單解釋這里的操作就是:首先獲取`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);
    //解析子節(jié)點
    List<XNode> resultChildren = resultMapNode.getChildren();

    for (XNode resultChild : resultChildren) {
      //單獨處理構(gòu)造函數(shù)節(jié)點
      if ("constructor".equals(resultChild.getName())) {
        //處理構(gòu)造函數(shù)節(jié)點  
        processConstructorElement(resultChild, typeClass, resultMappings);
         //單獨處理鑒定器節(jié)點 
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        //如果子節(jié)點為<id>,則將其保存起來,后續(xù)用來充當(dāng)equals的作用 
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        //處理其他的子節(jié)點  
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    //獲取此節(jié)點的id
    String id = resultMapNode.getStringAttribute("id",
            //不存在則自動生成一個唯一id                            
            resultMapNode.getValueBasedIdentifier());
    //獲取繼承節(jié)點信息
    String extend = resultMapNode.getStringAttribute("extends");
    //是否自動映射
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //創(chuàng)建解析類
    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>

可以發(fā)現(xiàn)resultMap中可以嵌套<constructor>或者<collection>

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

那如果要完整的解析這些嵌套的東西,最好的辦法就是遞歸,

這就是為什么前面會有3中type的獲取,因為這個方法不僅僅是在解析<resultMap>,還會被遞歸調(diào)用來解析<association> \ <collection> 等等。

先跳過其他的代碼,我們先看看解析其他子節(jié)點

XMLMapperBuilder###buildResultMappingFromContext()

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    //判斷時候需要通過構(gòu)造方法賦值
    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));
 //繼續(xù)獲取其他的屬性                                                       
    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())) {
      //先不解析動態(tài)SQL  
      if (context.getStringAttribute("select") == null) {
        //驗證collection節(jié)點是否包含必須要元素
        //必須包含resultMap 和 javaType其中一個  
        validateCollection(context, enclosingType);
        //調(diào)用最開始的方法,進行遞歸解析  
        ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
        return resultMap.getId();
      }
    }
    return null;
  }

這里就能能看出,已經(jīng)開始遞歸調(diào)用了

接下來,我們以<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一般都是父節(jié)點的type 
      //比如<resultMap type="test">
      //       <collection/>
      //那么enclosingType便是test  
      //如果有些節(jié)點沒有配置type,允許的情況下,可以直接使用父節(jié)點的type
      //例如:
      //    <discriminator javaType="int" column="draft">
      //        <case value="1" resultType="DraftPost"/>
      //    </discriminator>
      // case節(jié)點中就沒有配置type  
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    //添加已經(jīng)解析過的resultMap
    resultMappings.addAll(additionalResultMappings);
    //繼續(xù)解析子節(jié)點
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      //處理構(gòu)造方法  
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      }
      //處理鑒定器  
       else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        //處理其他子節(jié)點   
        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");
    //構(gòu)建resultMap
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      //最后的解析  
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

接下來看其他細(xì)節(jié):

當(dāng)子節(jié)點解析完了以后,會將子節(jié)點添加到resultMapping中,然后再解析最大的resultMap

MapperBuilderAssiant###addResultMap()

//resultMapResolver.resolve();內(nèi)部調(diào)用的此方法
public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    //首先給當(dāng)前標(biāo)簽加上命名空間前綴
    id = applyCurrentNamespace(id, false);
    //然后給繼承的標(biāo)簽加上命名空間前綴
    //從這個當(dāng)前前綴可以看出來,繼承只能繼承當(dāng)前命名空間的元素
    extend = applyCurrentNamespace(extend, true);
    //判斷是否有集成的屬性
    if (extend != null) {
      //如果所繼承的屬性還沒有解析,那么拋出指定異常,稍后再解析  
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      //通過繼承找到對應(yīng)的resultMap  
      ResultMap resultMap = configuration.getResultMap(extend);
      //獲取這個resultMap id 下對應(yīng)的所有resultMap (包括嵌套resultMap)
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      //去除重復(fù)resultMap
      extendedResultMappings.removeAll(resultMappings);
      // Remove parent constructor if this resultMap declares a constructor.
      boolean declaresConstructor = false;
      //檢查是否需要使用帶參構(gòu)造方法構(gòu)造resultMap
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      //如果需要使用構(gòu)造方法創(chuàng)鍵`resultMap type`,那么將此需要使用構(gòu)造方法的元素從繼承元素中刪除  
      if (declaresConstructor) {
        extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
      }
      resultMappings.addAll(extendedResultMappings);
    }
    //構(gòu)造resultMap
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    configuration.addResultMap(resultMap);
    return resultMap;
  }

這幾段代碼看著實在累,第一是比較復(fù)雜,第二個就是本來想要實現(xiàn)的功能也比較復(fù)雜。

這里可以看見MyBatis僅僅是將嵌套的resultMap分解為幾個reusltMap,然后放入list中,并且這里解析配置就真正的只是解析配置,沒有做任何多余的事、

同時可以看出來,MyBatis的模塊劃分是非常好的,

XMLConfigBuilder->XMLMapperBuilder->MapperBuilderAssistant

XMLMapperBuilder只用負(fù)責(zé)讀取配置文件,而將配置文件生成對像則交給MapperBuilderAssistant

看上面的代碼,resultMap也只解析了靜態(tài)部分能夠解析的地方,而需要動態(tài)生成的則直接原封不動的放入了Configuration類中

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 2019-06-09 時間管理 一天24小時的管理很重要,因為掌控時間就等于掌控了人生。 6:00——6:10 【...
    A守望天下閱讀 176評論 1 2
  • 用了大量的時間終于把科目二考完了,結(jié)果科目三才學(xué)了沒幾天就要去考試了,這時你的心里是不是有著一絲的不安?其實科目三...
    張大婷123閱讀 335評論 0 0

友情鏈接更多精彩內(nèi)容