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 ?元素的概念視圖。
屬性 | 描述 |
---|---|
id
|
當前命名空間中的一個唯一標識,用于標識一個結果映射。 |
type
|
類的完全限定名, 或者一個類型別名(關于內置的類型別名,可以參考上面的表格)。 |
autoMapping
|
如果設置這個屬性,MyBatis 將會為本結果映射開啟或者關閉自動映射。 這個屬性會覆蓋全局的屬性 autoMappingBehavior。默認值:未設置(unset)。 |
最好逐步建立結果映射。單元測試可以在這個過程中起到很大幫助。 如果你嘗試一次性創(chuàng)建像上面示例那么巨大的結果映射,不僅容易出錯,難度也會直線上升。 所以,從最簡單的形態(tài)開始,逐步迭代。而且別忘了單元測試! 有時候,框架的行為像是一個黑盒子(無論是否開源)。因此,為了確保實現(xiàn)的行為與你的期望相一致,最好編寫單元測試。 并且單元測試在提交 bug 時也能起到很大的作用。
下一部分將詳細說明每個元素。
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
這些元素是結果映射的基礎。id 和 result 元素都將一個列的值映射到一個簡單數(shù)據(jù)類型(String, int, double, Date 等)的屬性或字段。
這兩者之間的唯一不同是,id 元素對應的屬性會被標記為對象的標識符,在比較對象實例時使用。 這樣可以提高整體的性能,尤其是進行緩存和嵌套結果映射(也就是連接映射)的時候。
兩個元素都有一些屬性:
屬性 | 描述 |
---|---|
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)類的全限定名,或者是類型別名。 |
為了以后可能的使用場景,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 元素。參看上面的解釋。 |
更多建議: