天天看點

mybatis源碼-原來resultMap解析完是這樣

mybatis源碼-原來resultMap解析完是這樣

使用了這麼久的mybatis, 你有深入了解過

@

目錄

  • 1 兩個基礎類
    • 1.1 列映射類ResultMapping
    • 1.2 結果集映射類ResultMap
  • 2. 解析
    • 2.1 入口函數
    • 2.2 解析流程
    • 2.3 擷取 id
    • 2.4 解析結果集的類型
    • 2.5 擷取繼承結果集和自動映射
    • 2.6 解析 的子節點
    • 2.7 建立 ResultMap 對象
  • 3 解析結果
  • 4 一起來學習 mybatis

在 select 語句中查詢得到的是一張二維表, 水準方向上看是一個個字段, 垂直方向上看是一條條記錄。

作為面向對象的語言, Java 中的的對象是根據類定義建立的。 類之間的引用關系可以認為是嵌套的關系。

在 mybatis 中, resultMap 節點定義了結果集和結果對象(JavaBean)之間的映射規則。

本文主要講解的是 resultMap 的解析。

在閱讀本文之前, 最好能對這兩個類有相應的了解。

ResultMapping 對象記錄了結果集中一列與隊友JavaBean中一個屬性的對應關系。

mybatis源碼-原來resultMap解析完是這樣

更多詳情, 請參考mybatis百科-列映射類ResultMapping

ResultMap

對應的是結果集 <resultMap>中的一個結果集。 其基本組成部分中, 含有

ResultMapping

對象。

其組成大緻如下:

mybatis源碼-原來resultMap解析完是這樣

更多詳情, 請參考mybatis百科-結果集映射類ResultMap

resultMap 是 mapper.xml 檔案下的, 是以其是解析 Mapper 的一個環節。

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

解析<resultMap>, 由于<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 這個函數。其流程大體如下

mybatis源碼-原來resultMap解析完是這樣

對應的代碼

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
  
  /**
   * 處理 <resultMap> 節點, 将節點解析成 ResultMap 對象, 下面包含有 ResultMapping 對象組成的清單
   * @param resultMapNode resultMap 節點
   * @param additionalResultMappings 另外的 ResultMapping 列
   * @return ResultMap 對象
   * @throws Exception
   */
  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);
        // 處理 discriminator 節點
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        // 處理其餘節點, 如 id, result, assosation d等
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        // 建立 resultMapping 對象, 并添加到 resultMappings 中
        resultMappings.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 對于 resultMap 來說是很重要的, 它是一個身份辨別。 具有唯一性

// 擷取 ID , 預設值會拼裝所有父節點的 id 或 value 或 property。
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
           

這裡涉及到

XNode

對象中的兩個函數

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>Jim</first_name>
  <last_name>Smith</last_name>
  <birth_date>
    <year>1970</year>
    <month>6</month>
    <day>15</day>
  </birth_date>
  <height units="ft">5.8</height>
  <weight units="lbs">200</weight>
  <active>true</active>
</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)的 <resultMap> 的屬性, 隻有 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 的時候可有可無的。

先看 DTD 限制

<!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)>
           

可以有以下幾個子節點:

子節點 數量
constructor 出現 0 次或 1 次
id 出現 0 次或 多 次
result
association
collection
discriminator 出現 0次或 1 次

子節點解析過程很簡單

mybatis源碼-原來resultMap解析完是這樣

根據類型進行解析, 最後獲得 resultMappings (List),有可能會獲得 discriminator(Discriminator)。

// 建立一個 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;
  }
           

有如下的資料庫表

mybatis源碼-原來resultMap解析完是這樣

通過代碼生成器生成 XML 和 Mapper。

添加結果集

mybatis源碼-原來resultMap解析完是這樣

對應的 sql

mybatis源碼-原來resultMap解析完是這樣

則最後解析出的結果

mybatis源碼-原來resultMap解析完是這樣

你想不想來學習 mybatis? 學習其使用和源碼呢?那麼, 在部落格園關注我吧!!

我自己打算把這個源碼系列更新完畢, 同時會更新相應的注釋。快去 star 吧!!

mybatis最新源碼和注釋

作者:阿進的寫字台

出處:https://www.cnblogs.com/homejim/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。