MyBatis 3 結(jié)果映射-基本方法

2022-04-09 14:38 更新

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

之前你已經(jīng)見(jiàn)過(guò)簡(jiǎn)單映射語(yǔ)句的示例,它們沒(méi)有顯式指定 ?resultMap?。比如:

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

上述語(yǔ)句只是簡(jiǎn)單地將所有的列映射到 ?HashMap ?的鍵上,這由 ?resultType ?屬性指定。雖然在大部分情況下都?jí)蛴茫?nbsp;?HashMap ?并不是一個(gè)很好的領(lǐng)域模型。你的程序更可能會(huì)使用 ?JavaBean ?或 ?POJO?(?Plain Old Java Objects?,普通老式 Java 對(duì)象)作為領(lǐng)域模型。MyBatis 對(duì)兩者都提供了支持。看看下面這個(gè) ?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ī)范,上面這個(gè)類有 3 個(gè)屬性:?id?,?username ?和 ?hashedPassword?。這些屬性會(huì)對(duì)應(yīng)到 ?select ?語(yǔ)句中的列名。

這樣的一個(gè) ?JavaBean ?可以被映射到 ?ResultSet?,就像映射到 ?HashMap ?一樣簡(jiǎn)單。

<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 會(huì)在幕后自動(dòng)創(chuàng)建一個(gè) ?ResultMap?,再根據(jù)屬性名來(lái)映射列到 ?JavaBean ?的屬性上。如果列名和屬性名不能匹配上,可以在 ?SELECT ?語(yǔ)句中設(shè)置列別名(這是一個(gè)基本的 SQL 特性)來(lái)完成匹配。比如:

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

在學(xué)習(xí)了上面的知識(shí)后,你會(huì)發(fā)現(xiàn)上面的例子沒(méi)有一個(gè)需要顯式配置 ?ResultMap?,這就是 ?ResultMap ?的優(yōu)秀之處——你完全可以不用顯式地配置它們。 雖然上面的例子不用顯式配置 ?ResultMap?。 但為了講解,我們來(lái)看看如果在剛剛的示例中,顯式使用外部的 ?resultMap ?會(huì)怎樣,這也是解決列名不匹配的另外一種方式。

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

然后在引用它的語(yǔ)句中設(shè)置 ?resultMap ?屬性就行了(注意我們?nèi)サ袅?nbsp;?resultType ?屬性)。比如:

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

高級(jí)結(jié)果映射

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

比如,我們?nèi)绾斡成湎旅孢@個(gè)語(yǔ)句?

<!-- 非常復(fù)雜的語(yǔ)句 -->
<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>

你可能想把它映射到一個(gè)智能的對(duì)象模型,這個(gè)對(duì)象表示了一篇博客,它由某位作者所寫(xiě),有很多的博文,每篇博文有零或多條的評(píng)論和標(biāo)簽。 我們先來(lái)看看下面這個(gè)完整的例子,它是一個(gè)非常復(fù)雜的結(jié)果映射(假設(shè)作者,博客,博文,評(píng)論和標(biāo)簽都是類型別名)。 不用緊張,我們會(huì)一步一步地來(lái)說(shuō)明。雖然它看起來(lái)令人望而生畏,但其實(shí)非常簡(jiǎn)單。

<!-- 非常復(fù)雜的結(jié)果映射 -->
<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 ??元素有很多子元素和一個(gè)值得深入探討的結(jié)構(gòu)。 下面是?resultMap ?元素的概念視圖。

結(jié)果映射(resultMap)

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

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

下一部分將詳細(xì)說(shuō)明每個(gè)元素。

id & result

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

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

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

兩個(gè)元素都有一些屬性:

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

支持的 JDBC 類型

為了以后可能的使用場(chǎng)景,MyBatis 通過(guò)內(nèi)置的 ?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

構(gòu)造方法

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

看看下面這個(gè)構(gòu)造方法:

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

為了將結(jié)果注入構(gòu)造方法,MyBatis 需要通過(guò)某種方式定位相應(yīng)的構(gòu)造方法。 在下面的例子中,MyBatis 搜索一個(gè)聲明了三個(gè)形參的構(gòu)造方法,參數(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>

當(dāng)你在處理一個(gè)帶有多個(gè)形參的構(gòu)造方法時(shí),很容易搞亂 ?arg ?元素的順序。 從版本 3.4.3 開(kāi)始,可以在指定參數(shù)名稱的前提下,以任意順序編寫(xiě) ?arg ?元素。 為了通過(guò)名稱來(lái)引用構(gòu)造方法參數(shù),你可以添加 ?@Param? 注解,或者使用 '?-parameters?' 編譯選項(xiàng)并啟用 ?useActualParamName ?選項(xiàng)(默認(rèn)開(kāi)啟)來(lái)編譯項(xiàng)目。下面是一個(gè)等價(jià)的例子,盡管函數(shù)簽名中第二和第三個(gè)形參的順序與 ?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ù)庫(kù)中的列名,或者是列的別名。一般情況下,這和傳遞給 resultSet.getString(columnName) 方法的參數(shù)一樣。
javaType 一個(gè) Java 類的完全限定名,或一個(gè)類型別名(關(guān)于內(nèi)置的類型別名,可以參考上面的表格)。 如果你映射到一個(gè) JavaBean,MyBatis 通??梢酝茢囝愋汀H欢?,如果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來(lái)保證行為與期望的相一致。
jdbcType JDBC 類型,所支持的 JDBC 類型參見(jiàn)這個(gè)表格之前的“支持的 JDBC 類型”。 只需要在可能執(zhí)行插入、更新和刪除的且允許空值的列上指定 JDBC 類型。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 編程,你需要對(duì)可能存在空值的列指定這個(gè)類型。
typeHandler 我們?cè)谇懊嬗懻撨^(guò)默認(rèn)的類型處理器。使用這個(gè)屬性,你可以覆蓋默認(rèn)的類型處理器。 這個(gè)屬性值是一個(gè)類型處理器實(shí)現(xiàn)類的完全限定名,或者是類型別名。
select 用于加載復(fù)雜類型屬性的映射語(yǔ)句的 ID,它會(huì)從 column 屬性中指定的列檢索數(shù)據(jù),作為參數(shù)傳遞給此 select 語(yǔ)句。具體請(qǐng)參考關(guān)聯(lián)元素。
resultMap 結(jié)果映射的 ID,可以將嵌套的結(jié)果集映射到一個(gè)合適的對(duì)象樹(shù)中。 它可以作為使用額外 select 語(yǔ)句的替代方案。它可以將多表連接操作的結(jié)果映射成一個(gè)單一的 ResultSet。這樣的 ResultSet 將會(huì)將包含重復(fù)或部分?jǐn)?shù)據(jù)重復(fù)的結(jié)果集。為了將結(jié)果集正確地映射到嵌套的對(duì)象樹(shù)中,MyBatis 允許你 “串聯(lián)”結(jié)果映射,以便解決嵌套結(jié)果集的問(wèn)題。想了解更多內(nèi)容,請(qǐng)參考下面的關(guān)聯(lián)元素。
name 構(gòu)造方法形參的名字。從 3.4.3 版本開(kāi)始,通過(guò)指定具體的參數(shù)名,你可以以任意順序?qū)懭?arg 元素。參看上面的解釋。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)