Mybatis Mapper.xml 配置文件中 resultMap 節點的源碼解析

相關文章

Mybatis 解析配置文件的源碼解析java

Mybatis 類型轉換源碼分析數據庫

Mybatis 數據源和數據庫鏈接池源碼解析(DataSource)mybatis

Mybatis Mapper 接口源碼解析(binding包)app

Mybatis 解析 SQL 源碼分析一源碼分析

前言

在上篇文章 Mybatis 解析 SQL 源碼分析一 介紹了 Maper.xml 配置文件的解析,可是沒有解析 resultMap 節點,由於該解析比較複雜,也比較難理解,全部單獨拿出來進行解析。post

在使用 Mybatis 的時候,都會使用resultMap節點來綁定列與bean屬性的對應關係,可是通常就只會使用其簡單的屬性,他還有一些比較複雜的屬性能夠實現一些高級的功能,在沒查看源碼以前,我也只會簡單的使用,不少高級的用法都沒有使用過,經過此次學習,但願能在工做使用,可以寫出簡潔高效的SQL。學習

resultMap的定義

先來看看 resultMap 節點的官方定義:測試

簡單的使用:fetch

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

會把列名和屬性名進行綁定,該節點一共有 4 個屬性:ui

1. id :表示該 resultMap,共其餘的語句調用

2. type:表示其對於的pojo類型,可使用別名,也可使用全限定類名

3. autoMapping:若是設置這個屬性,MyBatis將會爲這個ResultMap開啓或者關閉自動映射。這個屬性會覆蓋全局的屬性 autoMappingBehavior。默認值爲:unset。

4. extends:繼承,一個 resultMap 能夠繼承另外一個 resultMap,這個屬性是否是沒有用過 ? ^^

接下來看下它能夠有哪些子節點:

  • constructor - 用於注入結果到構造方法中
  • id – 標識ID列
  • result – 表示通常列
  • association – 關聯查詢
  • collection – 查詢集合
  • discriminator - 鑑別器:mybatis可使用discriminator判斷某列的值,而後根據某列的值改變封裝行爲

constructor 

在查詢數據庫獲得數據後,會把對應列的值賦值給javabean對象對應的屬性,默認狀況下mybatis會調用實體類的無參構造方法建立一個實體類,而後再給各個屬性賦值,若是沒有構造方法的時候,可使用 constructor 節點進行綁定,如現有以下的構造方法:

public Person(int id, String name, String job, int age) {
        this.id = id;
        this.name = name;
        this.job = job;
        this.age = age;
    }

 則,可使用 constructor  節點進行綁定:

<resultMap id="queryPersonMap" type="mybatis.pojo.Person" >
        <constructor>
            <idArg column="id" javaType="int"/>
            <arg column="name" javaType="string" />
            <arg column="job" javaType="string" />
            <arg column="age" javaType="int" />
        </constructor>
    </resultMap>

association 

關聯查詢,在級聯中有一對1、一對多、多對多等關係,association主要是用來解決一對一關係的,association 能夠有多種使用方式:

好比如今有一個 Person 類,它有一個 Address 屬性,關聯 Address 對象:

public class Person implements Serializable {

    private int id;

    private String name;

    private String job;

    private int age;

    private Address address;
}

public class Address {
    private int id;

    private String name;

    private long number;

}

關聯查詢方式一:

<resultMap id="queryPersonMap" type="mybatis.pojo.Person" >
        <id column="id" property="id"/>
        <result column="name" property="name" />
        <result column="job" property="job" />
        <result column="age" property="age"/>
        <association property="address" column="address_id" javaType="mybatis.pojo.Address" select="queryAddress" />
    </resultMap>

    <select id="queryAddress" resultType="mybatis.pojo.Address">
        select * from address where id = #{id}
    </select>

關聯查詢方式二:

<resultMap id="queryPersonMap" type="mybatis.pojo.Person" >
        <id column="id" property="id"/>
        <result column="name" property="name" />
        <result column="job" property="job" />
        <result column="age" property="age"/>
        <association property="address" column="address_id" javaType="mybatis.pojo.Address" resultMap="addressMap"/>
    </resultMap>
    
    <resultMap id="addressMap" type="mybatis.pojo.Address">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="number" property="number"/>
    </resultMap>

關聯查詢方式三:

<resultMap id="queryPersonMap" type="mybatis.pojo.Person" >
        <id column="id" property="id"/>
        <result column="name" property="name" />
        <result column="job" property="job" />
        <result column="age" property="age"/>
        <association property="address" javaType="mybatis.pojo.Address">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="number" property="number"/>
        </association>
    </resultMap>

collection 

collection 集合,若是pojo對象有一個屬性是集合類型的,可使用collection 來進行查詢:

public class Person implements Serializable {

    private int id;

    private String name;

    private String job;

    private int age;

    private List<Address> addressList;
}
<resultMap id="queryPersonMap" type="mybatis.pojo.Person" >
        <id column="id" property="id"/>
        <result column="name" property="name" />
        <result column="job" property="job" />
        <result column="age" property="age"/>
        <collection property="addressList" javaType="ArrayList" ofType="mybatis.pojo.Address">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="number" property="number"/>
        </collection>
    </resultMap>

固然還有其餘的方法,具體能夠參考官網。

discriminator

鑑別器,mybatis可使用discriminator判斷某列的值,而後根據某列的值改變封裝行爲,有點像 Java的 switch 語句,鑑別器指定了 column 和 javaType 屬性。 列是 MyBatis 查找比較值的地方。 JavaType 是須要被用來保證等價測試的合適類型,

好比某列的值等於多少,則返回1,等於多少返回2等等。

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultMap="carResult"/>
    <case value="2" resultMap="truckResult"/>
    <case value="3" resultMap="vanResult"/>
    <case value="4" resultMap="suvResult"/>
  </discriminator>
</resultMap>

以上就是 resultMap 節點的所有使用方法,下面是一個比較複雜的例子,源碼解析會按照其來解析,例子來自於官方文檔。

<resultMap id="detailedBlogResultMap" type="Blog">
  <constructor>
    <idArg column="blog_id" javaType="int"/>
    <arg column="name" javaType="string" />
  </constructor>

  <id column="id" property="id" />
  <result property="title" column="blog_title"/>

  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
  </association>

  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
  </collection>
  
  <discriminator javaType="int" column="draft">
    <case value="1" resultType="DraftPost"/>
  </discriminator>
</resultMap>

resultMap 源碼解析

首先須要說明的是,一個 resultMap 節點會解析成一個 ResultMap 對象,而每一個子節點(除了discriminator節點)會被解析成 ResultMapping 對象,即一個 ResultMap 包含的是 ResultMapping 對象的集合。

先來看看 ResultMapping 的一個聲明:

public class ResultMapping {
  // configuration 對象
  private Configuration configuration;
  private String property;
  private String column;
  private Class<?> javaType;
  private JdbcType jdbcType;
  private TypeHandler<?> typeHandler;
  // 對應的是 resultMap 屬性,經過id來引用其餘的resultMap
  private String nestedResultMapId;
  // 對應的是 select 屬性,經過id來引用其餘的select節點的定義
  private String nestedQueryId;
  private Set<String> notNullColumns;
  private String columnPrefix;
  // 處理後的標誌,標誌有兩個 id和constructor
  private List<ResultFlag> flags;
  // 對應節點的column屬性拆分後生成的結果,composites.size()>0會使column爲null
  private List<ResultMapping> composites;
  private String resultSet;
  private String foreignColumn;
  private boolean lazy;
|

ResultMap 的聲明以下:

public class ResultMap {
  // ID,表示一個resultMap
  private String id;
  // 該resultMap對應的Javabean類型
  private Class<?> type;
  // 對應的是除了discriminator節點外的其餘節點
  private List<ResultMapping> resultMappings;
  // id 節點的映射集合
  private List<ResultMapping> idResultMappings;
  // 構造節點的集合
  private List<ResultMapping> constructorResultMappings;
  // 記錄了映射關係中 不帶有contructot節點的的映射關係
  private List<ResultMapping> propertyResultMappings;
  // column集合
  private Set<String> mappedColumns;
  // discriminator 節點
  private Discriminator discriminator;
  private boolean hasNestedResultMaps;
  private boolean hasNestedQueries;
  private Boolean autoMapping;
}

解析:

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

  private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
        // 解析每一個 resultMap 節點
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

  private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    // 注意這裏傳入的是一個空的集合
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  }

主要的解析方法:

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // ID 屬性
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
    // type屬性
    String type = resultMapNode.getStringAttribute("type",resultMapNode.getStringAttribute("ofType",        resultMapNode.getStringAttribute("resultType",resultMapNode.getStringAttribute("javaType"))));
    // extends 屬性
    String extend = resultMapNode.getStringAttribute("extends");
    // autoMapping 屬性
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // 從註冊的類型管理器裏面查找對應的類型
    Class<?> typeClass = resolveClass(type);
    // discriminator 節點
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    // 處理子節點
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        // 處理 constructor 節點
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        // 處理discriminator節點
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        // 處理其餘節點,建立 resultMapping 對象並添加到集合中
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 建立表明該 resultMap 節點的 ResultMap 對象並添加到 ResultMap 集合中。
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      // 解析失敗,添加到集合,從新解析
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

處理 constructor 節點:

private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    List<XNode> argChildren = resultChild.getChildren();
    for (XNode argChild : argChildren) {
      List<ResultFlag> flags = new ArrayList<ResultFlag>();
      // 向集合中添加 contrucator 標誌
      flags.add(ResultFlag.CONSTRUCTOR);
      if ("idArg".equals(argChild.getName())) {
        // 添加id標誌
        flags.add(ResultFlag.ID);
      }
      // 建立 ResultMapping 對象並添加到集合中
      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
  }

建立 ResultMapping 對象:

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    // 解析節點的屬性
    String 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"));
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    // 對應的 typeHandler 類型
    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);
  }

以後是建立 ResultMapped 對象並添加到集合中:

ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
// 調用的使用 builderAssistant 的 addResultMap 方法
return resultMapResolver.resolve();
public ResultMap addResultMap(String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
    // 爲 id 加上 namespace即 namespace.id
    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);
      List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
      // 由於上面添加過一次,如今要刪除重複的
      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;
        }
      }
      if (declaresConstructor) {
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      resultMappings.addAll(extendedResultMappings);
    }
    // 建立 resultMap 
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    // 添加到集合
    configuration.addResultMap(resultMap);
    return resultMap;
  }

到這裏,就把 resultMap 節點解析完畢了,以後在解析 Mapper.xml 文件的其餘節點,參考 Mybatis 解析 SQL 源碼分析一

相關文章
相關標籤/搜索