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>
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 ?元素的概念視圖。
屬性 | 描述 |
---|---|
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 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è)元素都有一些屬性:
屬性 | 描述 |
---|---|
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)類的全限定名,或者是類型別名。 |
為了以后可能的使用場(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
|
通過(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 元素。參看上面的解釋。 |
更多建議: