13 myBatis-05-高級ResultMap

1、體驗例子

如果要映射如下的複雜語句:

<!-- 非常複雜的語句 -->
<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>

映射代碼:Blog實體類中也需要包含關聯的相關屬性。

<!-- 非常複雜的結果映射 -->
<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>

2、語法結構介紹

 

2.1、語法解讀 - constructor

用來完成對構造函數的參數的傳值。

例如:

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

配置:

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

2.2、語法解讀 - id & result

id 和 result 元素都將一個列的值映射到一個簡單數據類型(String, int, double, Date 等)的屬性或字段。
這兩者之間的唯一不同是,id 元素對應的屬性會被標記爲對象的標識符,在比較對象實例時使用。 這樣可以提高整體的性能,尤其是進行緩存和嵌套結果映射(也就是連接映射)的時候。

屬性描述
property 映射到列結果的字段或屬性。如果 JavaBean 有這個名字的屬性(property),會先使用該屬性。否則 MyBatis 將會尋找給定名稱的字段(field)。 無論是哪一種情形,你都可以使用常見的點式分隔形式進行復雜屬性導航。 比如,你可以這樣映射一些簡單的東西:“username”,或者映射到一些複雜的東西上:“address.street.number”。
column 數據庫中的列名,或者是列的別名。一般情況下,這和傳遞給 resultSet.getString(columnName) 方法的參數一樣。
javaType 一個 Java 類的全限定名,或一個類型別名(關於內置的類型別名,可以參考上面的表格)。 如果你映射到一個 JavaBean,MyBatis 通常可以推斷類型。然而,如果你映射到的是 HashMap,那麼你應該明確地指定 javaType 來保證行爲與期望的相一致。
jdbcType JDBC 類型,所支持的 JDBC 類型參見這個表格之後的“支持的 JDBC 類型”。 只需要在可能執行插入、更新和刪除的且允許空值的列上指定 JDBC 類型。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 編程,你需要對可以爲空值的列指定這個類型。
typeHandler 我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的類型處理器。 這個屬性值是一個類型處理器實現類的全限定名,或者是類型別名。

2.3、語法解讀 - 關聯association

當某個類內部包含另一個類是,要一起賦值,則需要通過關聯完成映射配置。

需要指定類內部的屬性名和屬性類型,內部配置關聯類屬性和查詢字段的對應關係。

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

2.3.1、語法解讀 - 關聯 - 嵌套Select查詢

內部關聯對象通過另一個單獨的Select查詢獲取數據,不通過表關聯查詢。

屬性描述
column 數據庫中的列名,或者是列的別名。一般情況下,這和傳遞給 resultSet.getString(columnName) 方法的參數一樣。 注意:在使用複合主鍵的時候,你可以使用 column="{prop1=col1,prop2=col2}" 這樣的語法來指定多個傳遞給嵌套 Select 查詢語句的列名。這會使得 prop1 和 prop2 作爲參數對象,被設置爲對應嵌套 Select 語句的參數。
select 用於加載複雜類型屬性的映射語句的 ID,它會從 column 屬性指定的列中檢索數據,作爲參數傳遞給目標 select 語句。 具體請參考下面的例子。注意:在使用複合主鍵的時候,你可以使用 column="{prop1=col1,prop2=col2}" 這樣的語法來指定多個傳遞給嵌套 Select 查詢語句的列名。這會使得 prop1 和 prop2 作爲參數對象,被設置爲對應嵌套 Select 語句的參數。
fetchType 可選的。有效值爲 lazy 和 eager。 指定屬性後,將在映射中忽略全局配置參數 lazyLoadingEnabled,使用屬性的值。

例子:這種方式被稱爲1+N查詢,執行的數據庫操作太多,性能不好。

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

2.3.2、語法解讀 - 關聯 - 嵌套結果查詢

通過表關聯查詢來同時查詢多個表內的數據。

屬性描述
resultMap 結果映射的 ID,可以將此關聯的嵌套結果集映射到一個合適的對象樹中。 它可以作爲使用額外 select 語句的替代方案。它可以將多表連接操作的結果映射成一個單一的 ResultSet。這樣的 ResultSet 有部分數據是重複的。 爲了將結果集正確地映射到嵌套的對象樹中, MyBatis 允許你“串聯”結果映射,以便解決嵌套結果集的問題。使用嵌套結果映射的一個例子在表格以後。
columnPrefix 當連接多個表時,你可能會不得不使用列別名來避免在 ResultSet 中產生重複的列名。指定 columnPrefix 列名前綴允許你將帶有這些前綴的列映射到一個外部的結果映射中。 詳細說明請參考後面的例子。
notNullColumn 默認情況下,在至少一個被映射到屬性的列不爲空時,子對象纔會被創建。 你可以在這個屬性上指定非空的列來改變默認行爲,指定後,Mybatis 將只在這些列非空時才創建一個子對象。可以使用逗號分隔來指定多個列。默認值:未設置(unset)。
autoMapping 如果設置這個屬性,MyBatis 將會爲本結果映射開啓或者關閉自動映射。 這個屬性會覆蓋全局的屬性 autoMappingBehavior。注意,本屬性對外部的結果映射無效,所以不能搭配 select 或 resultMap 元素使用。默認值:未設置(unset)。

例子:

<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,可重用映射配置:

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

在上面的例子中,你可以看到,博客(Blog)作者(author)的關聯元素委託名爲 “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 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>

映射配置:

由於結果中的列名與結果映射中的列名不同。你需要指定 columnPrefix 以便重複使用該結果映射來映射 co-author 的結果。

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

2.3.3、語法解讀 - 關聯 - 多結果集

從版本 3.2.3 開始,MyBatis 提供了另一種解決 N+1 查詢問題的方法。

某些數據庫允許存儲過程返回多個結果集,或一次性執行多個語句,每個語句返回一個結果集。 我們可以利用這個特性,在不使用連接的情況下,只訪問數據庫一次就能獲得相關數據。

存儲過程getBlogsAndAuthors,一次執行多個查詢:

SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}

映射配置:在映射語句中,必須通過 resultSets 屬性爲每個結果集指定一個名字,多個名字使用逗號隔開。

<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
  {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

現在我們可以指定使用 “authors” 結果集的數據來填充 “author” 關聯:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="email" column="email"/>
    <result property="bio" column="bio"/>
  </association>
</resultMap>

2.4、語法解讀 - 集合collection

一個類內部包含另一個類的集合變量。 

一個博客包含多篇文章:

private List<Post> posts;

和關聯的用法基本差不多。

2.4.1、語法解讀 - 集合 - 嵌套Select查詢

分開查詢,性能不好。

首先,讓我們看看如何使用嵌套 Select 查詢來爲博客加載文章。

<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="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

這裏使用的是集合元素。 接下來你會注意到有一個新的 “ofType” 屬性。這個屬性非常重要,它用來將 JavaBean(或字段)屬性的類型和集合存儲的類型區分開來。 所以你可以按照下面這樣來閱讀映射:

讀作: “posts 是一個存儲 Post 的 ArrayList 集合” 

2.4.2、語法解讀 - 集合 - 嵌套結果映射

合併查詢:

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

可重用的映射配置:

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

2.4.3、語法解讀 - 集合 -多結果集

存儲過程getBlogsAndAuthors,一次執行多個查詢:

SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}

映射配置:在映射語句中,必須通過 resultSets 屬性爲每個結果集指定一個名字,多個名字使用逗號隔開。

<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
  {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

映射配置:我們指定 “posts” 集合將會使用存儲在 “posts” 結果集中的數據進行填充

<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
    <id property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="body" column="body"/>
  </collection>
</resultMap>

2.5、語法解讀 - 鑑別器discriminator

根據某個字段的不同值返回不同的映射結果。根據不同值映射不同的車輛類型。

類內部變量需要是所有類型的父類,每個不同的子類有自己個性化的屬性需要進行值映射。

<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 會從結果集中得到每條記錄,然後比較它的 vehicle type 值。 如果它匹配任意一個鑑別器的 case,就會使用這個 case 指定的結果映射。 這個過程是互斥的,也就是說,剩餘的結果映射將被忽略(除非它是擴展的,我們將在稍後討論它)。 如果不能匹配任何一個 case,MyBatis 就只會使用鑑別器塊外定義的結果映射。

3、自動映射

在簡單的場景下,MyBatis 可以爲你自動映射查詢結果。

通常數據庫列使用大寫字母組成的單詞命名,單詞間用下劃線分隔;而 Java 屬性一般遵循駝峯命名法約定。爲了在這兩種命名方式之間啓用自動映射,需要將 mapUnderscoreToCamelCase 設置爲 true。

甚至在提供了結果映射後,自動映射也能工作。在這種情況下,對於每一個結果映射,在 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 - 對除在內部定義了嵌套結果映射(也就是連接的屬性)以外的屬性進行映射
  • FULL - 自動映射所有屬性。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章