MyBatis 3 結果映射-基本方法

2022-04-09 14:38 更新

resultMap ?元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 ?JDBC ResultSets? 數(shù)據(jù)提取代碼中解放出來,并在一些情形下允許你進行一些 ?JDBC ?不支持的操作。實際上,在為一些比如連接的復雜語句編寫映射代碼的時候,一份 ?resultMap ?能夠代替實現(xiàn)同等功能的數(shù)千行代碼。?ResultMap ?的設計思想是,對簡單的語句做到零配置,對于復雜一點的語句,只需要描述語句之間的關系就行了。

之前你已經(jīng)見過簡單映射語句的示例,它們沒有顯式指定 ?resultMap?。比如:

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

上述語句只是簡單地將所有的列映射到 ?HashMap ?的鍵上,這由 ?resultType ?屬性指定。雖然在大部分情況下都夠用,但是 ?HashMap ?并不是一個很好的領域模型。你的程序更可能會使用 ?JavaBean ?或 ?POJO?(?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 ?的規(guī)范,上面這個類有 3 個屬性:?id?,?username ?和 ?hashedPassword?。這些屬性會對應到 ?select ?語句中的列名。

這樣的一個 ?JavaBean ?可以被映射到 ?ResultSet?,就像映射到 ?HashMap ?一樣簡單。

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

類型別名是你的好幫手。使用它們,你就可以不用輸入類的全限定名了。比如:

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

<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

在這些情況下,MyBatis 會在幕后自動創(chuàng)建一個 ?ResultMap?,再根據(jù)屬性名來映射列到 ?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>

在學習了上面的知識后,你會發(fā)現(xiàn)上面的例子沒有一個需要顯式配置 ?ResultMap?,這就是 ?ResultMap ?的優(yōu)秀之處——你完全可以不用顯式地配置它們。 雖然上面的例子不用顯式配置 ?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>     

然后在引用它的語句中設置 ?resultMap ?屬性就行了(注意我們去掉了 ?resultType ?屬性)。比如:

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

高級結果映射

MyBatis 創(chuàng)建時的一個思想是:數(shù)據(jù)庫不可能永遠是你所想或所需的那個樣子。 我們希望每個數(shù)據(jù)庫都具備良好的第三范式或 ?BCNF ?范式,可惜它們并不都是那樣。 如果能有一種數(shù)據(jù)庫映射模式,完美適配所有的應用程序,那就太好了,但可惜也沒有。 而 ?ResultMap ?就是 MyBatis 對這個問題的答案。

比如,我們如何映射下面這個語句?

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

你可能想把它映射到一個智能的對象模型,這個對象表示了一篇博客,它由某位作者所寫,有很多的博文,每篇博文有零或多條的評論和標簽。 我們先來看看下面這個完整的例子,它是一個非常復雜的結果映射(假設作者,博客,博文,評論和標簽都是類型別名)。 不用緊張,我們會一步一步地來說明。雖然它看起來令人望而生畏,但其實非常簡單。

<!-- 非常復雜的結果映射 -->
<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 參數(shù);標記出作為 ID 的結果可以幫助提高整體性能?arg ?- 將被注入到構造方法的一個普通結果
  • ?id ?– 一個 ID 結果;標記出作為 ID 的結果可以幫助提高整體性能
  • ?result ?– 注入到字段或 ?JavaBean ?屬性的普通結果
  • ?association ?– 一個復雜類型的關聯(lián);許多結果將包裝成這種類型
  • 嵌套結果映射 – 關聯(lián)可以是 ?resultMap ?元素,或是對其它結果映射的引用
  • ?collection ?– 一個復雜類型的集合
  • 嵌套結果映射 – 集合可以是 ?resultMap ?元素,或是對其它結果映射的引用
  • ?discriminator ?– 使用結果值來決定使用哪個 ?resultMap?
  • ?case ?– 基于某些值的結果映射嵌套結果映射 – ?case ?也是一個結果映射,因此具有相同的結構和元素;或者引用其它的結果映射
ResultMap 的屬性列表
屬性 描述
id 當前命名空間中的一個唯一標識,用于標識一個結果映射。
type 類的完全限定名, 或者一個類型別名(關于內置的類型別名,可以參考上面的表格)。
autoMapping 如果設置這個屬性,MyBatis 將會為本結果映射開啟或者關閉自動映射。 這個屬性會覆蓋全局的屬性 autoMappingBehavior。默認值:未設置(unset)。

最好逐步建立結果映射。單元測試可以在這個過程中起到很大幫助。 如果你嘗試一次性創(chuàng)建像上面示例那么巨大的結果映射,不僅容易出錯,難度也會直線上升。 所以,從最簡單的形態(tài)開始,逐步迭代。而且別忘了單元測試! 有時候,框架的行為像是一個黑盒子(無論是否開源)。因此,為了確保實現(xiàn)的行為與你的期望相一致,最好編寫單元測試。 并且單元測試在提交 bug 時也能起到很大的作用。

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

id & result

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

這些元素是結果映射的基礎。id 和 result 元素都將一個列的值映射到一個簡單數(shù)據(jù)類型(String, int, double, Date 等)的屬性或字段。

這兩者之間的唯一不同是,id 元素對應的屬性會被標記為對象的標識符,在比較對象實例時使用。 這樣可以提高整體的性能,尤其是進行緩存和嵌套結果映射(也就是連接映射)的時候。

兩個元素都有一些屬性:

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

支持的 JDBC 類型

為了以后可能的使用場景,MyBatis 通過內置的 ?jdbcType ?枚舉類型支持下面的 ?JDBC ?類型。

BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED
TINYINT REAL VARCHAR BINARY BLOB NVARCHAR
SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
BIGINT DECIMAL TIME NULL CURSOR ARRAY

構造方法

通過修改對象屬性的方式,可以滿足大多數(shù)的數(shù)據(jù)傳輸對象(?Data Transfer Object?, ?DTO?)以及絕大部分領域模型的要求。但有些情況下你想使用不可變類。 一般來說,很少改變或基本不變的包含引用或數(shù)據(jù)的表,很適合使用不可變類。 構造方法注入允許你在初始化時為類設置屬性的值,而不用暴露出公有方法。MyBatis 也支持私有屬性和私有 ?JavaBean ?屬性來完成注入,但有一些人更青睞于通過構造方法進行注入。 ?constructor ?元素就是為此而生的。

看看下面這個構造方法:

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

為了將結果注入構造方法,MyBatis 需要通過某種方式定位相應的構造方法。 在下面的例子中,MyBatis 搜索一個聲明了三個形參的構造方法,參數(shù)類型以 ?java.lang.Integer?, ?java.lang.String? 和 ?int ?的順序給出。

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

當你在處理一個帶有多個形參的構造方法時,很容易搞亂 ?arg ?元素的順序。 從版本 3.4.3 開始,可以在指定參數(shù)名稱的前提下,以任意順序編寫 ?arg ?元素。 為了通過名稱來引用構造方法參數(shù),你可以添加 ?@Param? 注解,或者使用 '?-parameters?' 編譯選項并啟用 ?useActualParamName ?選項(默認開啟)來編譯項目。下面是一個等價的例子,盡管函數(shù)簽名中第二和第三個形參的順序與 ?constructor ?元素中參數(shù)聲明的順序不匹配。

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

如果存在名稱和類型相同的屬性,那么可以省略 ?javaType ?。

剩余的屬性和規(guī)則和普通的 ?id ?和 ?result ?元素是一樣的。

屬性 描述
column 數(shù)據(jù)庫中的列名,或者是列的別名。一般情況下,這和傳遞給 resultSet.getString(columnName) 方法的參數(shù)一樣。
javaType 一個 Java 類的完全限定名,或一個類型別名(關于內置的類型別名,可以參考上面的表格)。 如果你映射到一個 JavaBean,MyBatis 通??梢酝茢囝愋汀H欢?,如果你映射到的是 HashMap,那么你應該明確地指定 javaType 來保證行為與期望的相一致。
jdbcType JDBC 類型,所支持的 JDBC 類型參見這個表格之前的“支持的 JDBC 類型”。 只需要在可能執(zhí)行插入、更新和刪除的且允許空值的列上指定 JDBC 類型。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 編程,你需要對可能存在空值的列指定這個類型。
typeHandler 我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的類型處理器。 這個屬性值是一個類型處理器實現(xiàn)類的完全限定名,或者是類型別名。
select 用于加載復雜類型屬性的映射語句的 ID,它會從 column 屬性中指定的列檢索數(shù)據(jù),作為參數(shù)傳遞給此 select 語句。具體請參考關聯(lián)元素。
resultMap 結果映射的 ID,可以將嵌套的結果集映射到一個合適的對象樹中。 它可以作為使用額外 select 語句的替代方案。它可以將多表連接操作的結果映射成一個單一的 ResultSet。這樣的 ResultSet 將會將包含重復或部分數(shù)據(jù)重復的結果集。為了將結果集正確地映射到嵌套的對象樹中,MyBatis 允許你 “串聯(lián)”結果映射,以便解決嵌套結果集的問題。想了解更多內容,請參考下面的關聯(lián)元素。
name 構造方法形參的名字。從 3.4.3 版本開始,通過指定具體的參數(shù)名,你可以以任意順序寫入 arg 元素。參看上面的解釋。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號