MyBatis系列之對映檔案中的ResultMaps

weixin_34054866發表於2016-10-27

resultMap 元素是 MyBatis 中最重要最強大的元素。它就是讓你遠離 90%的需要從結果 集中取出資料的 JDBC 程式碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支援的事 情。 事實上, 編寫相似於對複雜語句聯合對映這些等同的程式碼, 也許可以跨過上千行的程式碼。 ResultMap 的設計就是簡單語句不需要明確的結果對映,而很多複雜語句確實需要描述它們 的關係。

你已經看到簡單對映語句的示例了,但沒有明確的 resultMap。比如

<select id="selectUsers" resultType="map">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

這樣一個語句簡單作用於所有列被自動對映到 HashMap 的鍵上,這由 resultType 屬性 指定。這在很多情況下是有用的,但是 HashMap 不能很好描述一個領域模型。那樣你的應 用程式將會使用 JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 物件)來作為領域 模型。MyBatis 對兩者都支援。看看下面這個 JavaBean:

package com.someapp.model;
public class User {
  private int id;
  private String username;
  private String hashedPassword;
  
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getUsername() {
    return username;
  }
  public void setUsername(String username) {
    this.username = username;
  }
  public String getHashedPassword() {
    return hashedPassword;
  }
  public void setHashedPassword(String hashedPassword) {
    this.hashedPassword = hashedPassword;
  }
}

基於 JavaBean 的規範,上面這個類有 3 個屬性:id,username 和 hashedPassword。這些 在 select 語句中會精確匹配到列名。

這樣的一個 JavaBean 可以被對映到結果集,就像對映到 HashMap 一樣簡單。

<select id="selectUsers" resultType="com.someapp.model.User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

要記住型別別名是你的夥伴。使用它們你可以不用輸入類的全路徑。比如:

<!-- In mybatis-config.xml file -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

這些情況下,MyBatis 會在幕後自動建立一個 ResultMap,基於屬性名來對映列到 JavaBean 的屬性上。如果列名沒有精確匹配,你可以在列名上使用 select 字句的別名(一個 基本的 SQL 特性)來匹配標籤。比如:

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

ResultMap 最優秀的地方你已經瞭解了很多了,但是你還沒有真正的看到一個。這些簡 單的示例不需要比你看到的更多東西。 只是出於示例的原因, 讓我們來看看最後一個示例中 外部的 resultMap 是什麼樣子的,這也是解決列名不匹配的另外一種方式。

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

解釋如下:

  • 外層“id”屬性:即sql語句用引用的標識,其值需要在該檔案中具有唯一性。
  • 外層“type”屬性:即結果返回的目標物件(user),這裡需要定義為一個全路徑標識的物件,或者使用別名的物件。
  • 內層“id”標籤:即當前結果集合的主鍵。
  • 內層“property”屬性:即當前結果返回的目標物件的屬性(user中的屬性)。
  • 內層“column”屬性:即在資料庫中對應的列名。

引用它的語句使用 resultMap 屬性就行了(注意我們去掉了 resultType 屬性)。比如:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

如果世界總是這麼簡單就好了。

高階結果對映

MyBatis 建立的一個想法:資料庫不用永遠是你想要的或需要它們是什麼樣的。而我們 最喜歡的資料庫最好是第三正規化或 BCNF 模式,但它們有時不是。如果可能有一個單獨的 資料庫對映,所有應用程式都可以使用它,這是非常好的,但有時也不是。結果對映就是 MyBatis 提供處理這個問題的答案。

<!-- Very Complex Statement -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
  select
       B.id as blog_id,
       B.title as blog_title,
       B.author_id as blog_author_id,
       A.id as author_id,
       A.username as author_username,
       A.password as author_password,
       A.email as author_email,
       A.bio as author_bio,
       A.favourite_section as author_favourite_section,
       P.id as post_id,
       P.blog_id as post_blog_id,
       P.author_id as post_author_id,
       P.created_on as post_created_on,
       P.section as post_section,
       P.subject as post_subject,
       P.draft as draft,
       P.body as post_body,
       C.id as comment_id,
       C.post_id as comment_post_id,
       C.name as comment_name,
       C.comment as comment_text,
       T.id as tag_id,
       T.name as tag_name
  from Blog B
       left outer join Author A on B.author_id = A.id
       left outer join Post P on B.id = P.blog_id
       left outer join Comment C on P.id = C.post_id
       left outer join Post_Tag PT on PT.post_id = P.id
       left outer join Tag T on PT.tag_id = T.id
  where B.id = #{id}
</select>

你可能想把它對映到一個智慧的物件模型,包含一個作者寫的部落格,有很多的博文,每 篇博文有零條或多條的評論和標籤。 下面是一個完整的複雜結果對映例子 (假設作者, 部落格, 博文, 評論和標籤都是型別的別名) 我們來看看, 。 但是不用緊張, 我們會一步一步來說明。 當天最初它看起來令人生畏,但實際上非常簡單。

<!-- Very Complex Result Map -->
<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 元素有很多子元素和一個值得討論的結構。 下面是 resultMap 元素的概念檢視
resultMap

  • constructor - 類在例項化時,用來注入結果到構造方法中
    • idArg - ID 引數;標記結果作為 ID 可以幫助提高整體效能
    • arg - 注入到構造方法的一個普通結果
  • id – 一個 ID 結果;標記結果作為 ID 可以幫助提高整體效能
  • result – 注入到欄位或 JavaBean 屬性的普通結果
  • association – 一個複雜的型別關聯;許多結果將包成這種型別
    • 嵌入結果對映 – 結果對映自身的關聯,或者參考一個
  • collection – 複雜型別的集
    • 嵌入結果對映 – 結果對映自身的集,或者參考一個
  • discriminator – 使用結果值來決定使用哪個結果對映
    • case – 基於某些值的結果對映
      • 嵌入結果對映 – 這種情形結果也對映它本身,因此可以包含很多相 同的元素,或者它可以參照一個外部的結果對映。

resultMap節點的子節點id是用於標識該物件的id的,而result子節點則是用於標識一些簡單屬性的,其中的Column屬性表示從資料庫中查詢的屬性,Property則表示查詢出來的屬性對應的值賦給實體物件的哪個屬性。

下面一部分將詳細說明每個元素。

id & result
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

這些是結果對映最基本內容。id 和 result 都對映一個單獨列的值到簡單資料型別(字元 串,整型,雙精度浮點數,日期等)的單獨屬性或欄位。

這兩者之間的唯一不同是 id 表示的結果將是當比較物件例項時用到的標識屬性。這幫 助來改進整體表現,特別是快取和嵌入結果對映(也就是聯合對映) 。

屬性 描述
property 對映到列結果的欄位或屬性。如果匹配的是存在的,和給定名稱相同 的 JavaBeans 的屬性,那麼就會使用。否則 MyBatis 將會尋找給定名稱 property 的欄位。這兩種情形你可以使用通常點式的複雜屬性導航。比如,你 可以這樣對映一些東西: “username” ,或者對映到一些複雜的東西: “address.street.number” 。
column 從資料庫中得到的列名,或者是列名的重新命名標籤。這也是通常和會 傳遞給 resultSet.getString(columnName)方法引數中相同的字串。
javaType 一個 Java 類的完全限定名,或一個型別別名(參考上面內建型別別名 的列表) 。如果你對映到一個 JavaBean,MyBatis 通常可以斷定型別。 然而,如果你對映到的是 HashMap,那麼你應該明確地指定 javaType 來保證所需的行為。
jdbcType 在這個表格之後的所支援的 JDBC 型別列表中的型別。JDBC 型別是僅 僅需要對插入,更新和刪除操作可能為空的列進行處理。這是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 程式設計,你需要指定 這個型別-但僅僅對可能為空的值。
typeHandler 我們在前面討論過預設的型別處理器。使用這個屬性,你可以覆蓋默 認的型別處理器。這個屬性值是類的完全限定名或者是一個型別處理 器的實現,或者是型別別名。

每個都有一些屬性:

屬性 描述
property 對映到列結果的欄位或屬性。如果匹配的是存在的,和給定名稱相同 的 JavaBeans 的屬性,那麼就會使用。否則 MyBatis 將會尋找給定名稱 property 的欄位。這兩種情形你可以使用通常點式的複雜屬性導航。比如,你 可以這樣對映一些東西: “username” ,或者對映到一些複雜的東西: “address.street.number” 。
column 從資料庫中得到的列名,或者是列名的重新命名標籤。這也是通常和會 傳遞給 resultSet.getString(columnName)方法引數中相同的字串。
javaType 一個 Java 類的完全限定名,或一個型別別名(參考上面內建型別別名 的列表) 。如果你對映到一個 JavaBean,MyBatis 通常可以斷定型別。 然而,如果你對映到的是 HashMap,那麼你應該明確地指定 javaType 來保證所需的行為。
jdbcType 在這個表格之後的所支援的 JDBC 型別列表中的型別。JDBC 型別是僅 僅需要對插入,更新和刪除操作可能為空的列進行處理。這是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 程式設計,你需要指定 這個型別-但僅僅對可能為空的值。
typeHandler 我們在前面討論過預設的型別處理器。使用這個屬性,你可以覆蓋默 認的型別處理器。這個屬性值是類的完全限定名或者是一個型別處理 器的實現,或者是型別別名。
構造方法
<constructor>
   <idArg column="id" javaType="int"/>
   <arg column="username" javaType="String"/>
</constructor>

對於大多數資料傳輸物件(Data Transfer Object,DTO)型別,屬性可以起作用,而且像 你絕大多數的領域模型, 指令也許是你想使用一成不變的類的地方。 通常包含引用或查詢數 據的表很少或基本不變的話對一成不變的類來說是合適的。 構造方法注入允許你在初始化時 為類設定屬性的值,而不用暴露出公有方法。MyBatis 也支援私有屬性和私有 JavaBeans 屬 性來達到這個目的,但是一些人更青睞構造方法注入。構造方法元素支援這個。

看看下面這個構造方法:

public class User {
   //...
   public User(int id, String username) {
     //...
  }
//...
}

為了向這個構造方法中注入結果,MyBatis 需要通過它的引數的型別來標識構造方法。 Java 沒有自查(反射)引數名的方法。所以當建立一個構造方法元素時,保證引數是按順序 排列的,而且資料型別也是確定的。

<constructor>
   <idArg column="id" javaType="int"/>
   <arg column="username" javaType="String"/>
</constructor>

剩餘的屬性和規則和固定的 id 和 result 元素是相同的。

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

關聯元素處理“有一個”型別的關係。比如,在我們的示例中,一個部落格有一個使用者。 關聯對映就工作於這種結果之上。你指定了目標屬性,來獲取值的列,屬性的 java 型別(很 多情況下 MyBatis 可以自己算出來) ,如果需要的話還有 jdbc 型別,如果你想覆蓋或獲取的 結果值還需要型別控制器。

關聯中不同的是你需要告訴 MyBatis 如何載入關聯。MyBatis 在這方面會有兩種不同的 方式:

  • 巢狀查詢:通過執行另外一個 SQL 對映語句來返回預期的複雜型別。
  • 巢狀結果:使用巢狀結果對映來處理重複的聯合結果的子集。首先,然讓我們來檢視這個元素的屬性。所有的你都會看到,它和普通的只由 select 和resultMap 屬性的結果對映不同。
關聯的巢狀查詢
屬性 描述
column 來自資料庫的類名,或重新命名的列標籤。這和通常傳遞給 resultSet.getString(columnName)方法的字串是相同的。 column 注 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給巢狀查詢語 句。這會引起 prop1 和 prop2 以引數物件形式來設定給目標巢狀查詢語句。
select 另外一個對映語句的 ID,可以載入這個屬性對映需要的複雜型別。獲取的 在列屬性中指定的列的值將被傳遞給目標 select 語句作為引數。表格後面 有一個詳細的示例。 select 注 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給巢狀查詢語 句。這會引起 prop1 和 prop2 以引數物件形式來設定給目標巢狀查詢語句。
fetchType Optional. Valid values are lazy and eager. If present, it supersedes the global configuration parameter lazyLoadingEnabled for this mapping.

示例:

<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

我們有兩個查詢語句:一個來載入部落格,另外一個來載入作者,而且部落格的結果對映描 述了“selectAuthor”語句應該被用來載入它的 author 屬性。

其他所有的屬性將會被自動載入,假設它們的列和屬性名相匹配。

這種方式很簡單, 但是對於大型資料集合和列表將不會表現很好。 問題就是我們熟知的 “N+1 查詢問題”。概括地講,N+1 查詢問題可以是這樣引起的:

  • 你執行了一個單獨的 SQL 語句來獲取結果列表(就是“+1”)。
  • 對返回的每條記錄,你執行了一個查詢語句來為每個載入細節(就是“N”)。
    這個問題會導致成百上千的 SQL 語句被執行。這通常不是期望的。

MyBatis 能延遲載入這樣的查詢就是一個好處,因此你可以分散這些語句同時執行的消 耗。然而,如果你載入一個列表,之後迅速迭代來訪問巢狀的資料,你會呼叫所有的延遲加 載,這樣的行為可能是很糟糕的。

所以還有另外一種方法。

關聯的巢狀結果

在上面你已經看到了一個非常複雜的巢狀關聯的示例。 下面這個是一個非常簡單的示例 來說明它如何工作。代替了執行一個分離的語句,我們聯合部落格表和作者表在一起,就像:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>

注意這個聯合查詢, 以及採取保護來確保所有結果被唯一而且清晰的名字來重新命名。 這使得對映非常簡單。現在我們可以對映這個結果:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="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"/>
</resultMap>

在上面的示例中你可以看到部落格的作者關聯代表著“authorResult”結果對映來載入作 者例項。

非常重要: 在巢狀據誒過對映中 id 元素扮演了非常重要的角色。應應該通常指定一個 或多個屬性,它們可以用來唯一標識結果。實際上就是如果你離開她了,但是有一個嚴重的 效能問題時 MyBatis 仍然可以工作。選擇的屬性越少越好,它們可以唯一地標識結果。主鍵 就是一個顯而易見的選擇(儘管是聯合主鍵)。

現在,上面的示例用了外部的結果對映元素來對映關聯。這使得 Author 結果對映可以 重用。然而,如果你不需要重用它的話,或者你僅僅引用你所有的結果對映合到一個單獨描 述的結果對映中。你可以巢狀結果對映。這裡給出使用這種方式的相同示例:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <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"/>
  </association>
</resultMap>

如果某個部落格有合作作者(兩個作者)怎麼辦?

select語句

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio,
    CA.id           as co_author_id,
    CA.username     as co_author_username,
    CA.password     as co_author_password,
    CA.email        as co_author_email,
    CA.bio          as co_author_bio
  from Blog B
  left outer join Author A on B.author_id = A.id
  left outer join Author CA on B.co_author_id = CA.id
  where B.id = #{id}
</select>

關於author的resultMap

<resultMap id="authorResult" type="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"/>
</resultMap>

由於結果集當中的列名與查詢結果當中列名不一致,我們需要使用明確指定“columnPrefix”來重用這個結果集,以此來對映“聯合作者”的查詢結果。

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author"
    resultMap="authorResult" />
  <association property="coAuthor"
    resultMap="authorResult"
    columnPrefix="co_" />
</resultMap>

上面你已經看到了如何處理“有一個”型別關聯。但是“有很多個”是怎樣的?下面這 個部分就是來討論這個主題的。

集合
<collection property="posts" ofType="domain.blog.Post">
  <id property="id" column="post_id"/>
  <result property="subject" column="post_subject"/>
  <result property="body" column="post_body"/>
</collection>

集合元素的作用幾乎和關聯是相同的。實際上,它們也很相似,文件的異同是多餘的。 所以我們更多關注於它們的不同。

我們來繼續上面的示例,一個部落格只有一個作者。但是部落格有很多文章。在部落格類中, 這可以由下面這樣的寫法來表示:

private List<Post> posts;

要對映巢狀結果集合到 List 中,我們使用集合元素。就像關聯元素一樣,我們可以從 連線中使用巢狀查詢,或者巢狀結果。

集合的巢狀查詢

首先,讓我們看看使用巢狀查詢來為部落格載入文章。

<resultMap id="blogResult" type="Blog">
  <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Blog">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

這裡你應該注意很多東西,但大部分程式碼和上面的關聯元素是非常相似的。首先,你應 該注意我們使用的是集合元素。然後要注意那個新的“ofType”屬性。這個屬性用來區分 JavaBean(或欄位)屬性型別和集合包含的型別來說是很重要的。所以你可以讀出下面這個對映:

<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>

讀作: “在 Post 型別的 ArrayList 中的 posts 的集合。”

javaType 屬性是不需要的,因為 MyBatis 在很多情況下會為你算出來。所以你可以縮短 寫法:

<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>

集合的巢狀結果

至此,你可以猜測集合的巢狀結果是如何來工作的,因為它和關聯完全相同,除了它應 用了一個“ofType”屬性

<select id="selectBlog" resultMap="blogResult">
  select
  B.id as blog_id,
  B.title as blog_title,
  B.author_id as blog_author_id,
  P.id as post_id,
  P.subject as post_subject,
  P.body as post_body,
  from Blog B
  left outer join Post P on B.id = P.blog_id
  where B.id = #{id}
</select>

我們又一次聯合了部落格表和文章表,而且關注於保證特性,結果列標籤的簡單對映。現 在用文章對映集合對映部落格,可以簡單寫為:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

同樣,要記得 id 元素的重要性,如果你不記得了,請閱讀上面的關聯部分。

同樣, 如果你引用更長的形式允許你的結果對映的更多重用, 你可以使用下面這個替代 的對映:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
  <id property="id" column="id"/>
  <result property="subject" column="subject"/>
  <result property="body" column="body"/>
</resultMap>

鑑別器

<discriminator javaType="int" column="draft">
  <case value="1" resultType="DraftPost"/>
</discriminator>

有時一個單獨的資料庫查詢也許返回很多不同 (但是希望有些關聯) 資料型別的結果集。 鑑別器元素就是被設計來處理這個情況的, 還有包括類的繼承層次結構。 鑑別器非常容易理 解,因為它的表現很像 Java 語言中的 switch 語句

定義鑑別器指定了 column 和 javaType 屬性。 列是 MyBatis 查詢比較值的地方。 JavaType 是需要被用來保證等價測試的合適型別(儘管字串在很多情形下都會有用)。比如:

<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>

在這個示例中, MyBatis 會從結果集中得到每條記錄, 然後比較它的 vehicle 型別的值。 如果它匹配任何一個鑑別器的例項,那麼就使用這個例項指定的結果對映。換句話說,這樣 做完全是剩餘的結果對映被忽略(除非它被擴充套件,這在第二個示例中討論) 。如果沒有任何 一個例項相匹配,那麼 MyBatis 僅僅使用鑑別器塊外定義的結果對映。所以,如果 carResult 按如下宣告:

<resultMap id="carResult" type="Car">
  <result property="doorCount" column="door_count" />
</resultMap>

那麼只有 doorCount 屬性會被載入。這步完成後完整地允許鑑別器例項的獨立組,儘管 和父結果對映可能沒有什麼關係。這種情況下,我們當然知道 cars 和 vehicles 之間有關係, 如 Car 是一個 Vehicle 例項。因此,我們想要剩餘的屬性也被載入。我們設定的結果對映的 簡單改變如下。

<resultMap id="carResult" type="Car" extends="vehicleResult">
  <result property="doorCount" column="door_count" />
</resultMap>

現在 vehicleResult 和 carResult 的屬性都會被載入了。

儘管曾經有些人會發現這個外部對映定義會多少有一些令人厭煩之處。 因此還有另外一 種語法來做簡潔的對映風格。比如:

<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" resultType="carResult">
      <result property="doorCount" column="door_count" />
    </case>
    <case value="2" resultType="truckResult">
      <result property="boxSize" column="box_size" />
      <result property="extendedCab" column="extended_cab" />
    </case>
    <case value="3" resultType="vanResult">
      <result property="powerSlidingDoor" column="power_sliding_door" />
    </case>
    <case value="4" resultType="suvResult">
      <result property="allWheelDrive" column="all_wheel_drive" />
    </case>
  </discriminator>
</resultMap>

自動對映

正如你在前面一節看到的,在簡單的場景下,MyBatis可以替你自動對映查詢結果。 如果遇到複雜的場景,你需要構建一個result map。 但是在本節你將看到,你也可以混合使用這兩種策略。 讓我們到深一點的層面上看看自動對映是怎樣工作的。

當自動對映查詢結果時,MyBatis會獲取sql返回的列名並在java類中查詢相同名字的屬性(忽略大小寫)。 這意味著如果Mybatis發現了ID列和id屬性,Mybatis會將ID的值賦給id。

通常資料庫列使用大寫單詞命名,單詞間用下劃線分隔;而java屬性一般遵循駝峰命名法。 為了在這兩種命名方式之間啟用自動對映,需要將 mapUnderscoreToCamelCase設定為true。

自動對映甚至在特定的result map下也能工作。在這種情況下,對於每一個result map,所有的ResultSet提供的列, 如果沒有被手工對映,則將被自動對映。自動對映處理完畢後手工對映才會被處理。 在接下來的例子中, id 和 userName列將被自動對映, hashed_password 列將根據配置對映。

<select id="selectUsers" resultMap="userResultMap">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password
  from some_table
  where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
  <result property="password" column="hashed_password"/>
</resultMap>

有三種自動對映級別:

  • NONE - 禁用自動對映。只有手動對映屬性將被設定。
  • PARTIAL - 將會自動的匹配出除在內部(joins)內部巢狀的所有屬性。
  • FULL - 自動對映一切。
    預設級別是PARTIAL。

相關文章