MyBatis 的真正強(qiáng)大在于它的映射語句,也是它的魔力所在。由于它的異常強(qiáng)大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進(jìn)行對比,你會立即發(fā)現(xiàn)省掉了將近 95% 的代碼。MyBatis 就是針對 SQL 構(gòu)建的,并且比普通的方法做的更好。
SQL 映射文件有很少的幾個頂級元素(按照它們應(yīng)該被定義的順序):
cache
– 給定命名空間的緩存配置。cache-ref
– 其他命名空間緩存配置的引用。resultMap
– 是最復(fù)雜也是最強(qiáng)大的元素,用來描述如何從數(shù)據(jù)庫結(jié)果集中來加載對象。sql
– 可被其他語句引用的可重用語句塊。insert
– 映射插入語句update
– 映射更新語句delete
– 映射刪除語句select
– 映射查詢語句下一部分將從語句本身開始來描述每個元素的細(xì)節(jié)。
查詢語句是 MyBatis 中最常用的元素之一,光能把數(shù)據(jù)存到數(shù)據(jù)庫中價值并不大,如果還能重新取出來才有用,多數(shù)應(yīng)用也都是查詢比修改要頻繁。對每個插入、更新或刪除操作,通常對應(yīng)多個查詢操作。這是 MyBatis 的基本原則之一,也是將焦點和努力放到查詢和結(jié)果映射的原因。簡單查詢的 select 元素是非常簡單的。比如:
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
這個語句被稱作 selectPerson,接受一個 int(或 Integer)類型的參數(shù),并返回一個 HashMap 類型的對象,其中的鍵是列名,值便是結(jié)果行中的對應(yīng)值。
注意參數(shù)符號:
#{id}
這就告訴 MyBatis 創(chuàng)建一個預(yù)處理語句參數(shù),通過 JDBC,這樣的一個參數(shù)在 SQL 中會由一個 "?" 來標(biāo)識,并被傳遞到一個新的預(yù)處理語句中,就像這樣:
// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
當(dāng)然,這需要很多單獨的 JDBC 的代碼來提取結(jié)果并將它們映射到對象實例中,這就是 MyBatis 節(jié)省你時間的地方。我們需要深入了解參數(shù)和結(jié)果映射,細(xì)節(jié)部分我們下面來了解。
select 元素有很多屬性允許你配置,來決定每條語句的作用細(xì)節(jié)。
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10000"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
Select Attributes
屬性 | 描述 |
---|---|
id | 在命名空間中唯一的標(biāo)識符,可以被用來引用這條語句。 |
parameterType | 將會傳入這條語句的參數(shù)類的完全限定名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的參數(shù),默認(rèn)值為 unset。 |
resultType | 從這條語句中返回的期望類型的類的完全限定名或別名。注意如果是集合情形,那應(yīng)該是集合可以包含的類型,而不能是集合本身。使用 resultType 或 resultMap,但不能同時使用。 |
resultMap | 外部 resultMap 的命名引用。結(jié)果集的映射是 MyBatis 最強(qiáng)大的特性,對其有一個很好的理解的話,許多復(fù)雜映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同時使用。 |
flushCache | 將其設(shè)置為 true,任何時候只要語句被調(diào)用,都會導(dǎo)致本地緩存和二級緩存都會被清空,默認(rèn)值:false。 |
useCache | 將其設(shè)置為 true,將會導(dǎo)致本條語句的結(jié)果被二級緩存,默認(rèn)值:對 select 元素為 true。 |
timeout | 這個設(shè)置是在拋出異常之前,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)。默認(rèn)值為 unset(依賴驅(qū)動)。 |
fetchSize | 這是嘗試影響驅(qū)動程序每次批量返回的結(jié)果行數(shù)和這個設(shè)置值相等。默認(rèn)值為 unset(依賴驅(qū)動)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認(rèn)值:PREPARED。 |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一個,默認(rèn)值為 unset (依賴驅(qū)動)。 |
databaseId | 如果配置了 databaseIdProvider,MyBatis 會加載所有的不帶 databaseId 或匹配當(dāng)前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
resultOrdered | 這個設(shè)置僅針對嵌套結(jié)果 select 語句適用:如果為 true,就是假設(shè)包含了嵌套結(jié)果集或是分組了,這樣的話當(dāng)返回一個主結(jié)果行的時候,就不會發(fā)生有對前面結(jié)果集的引用的情況。這就使得在獲取嵌套的結(jié)果集的時候不至于導(dǎo)致內(nèi)存不夠用。默認(rèn)值:false。 |
resultSets | 這個設(shè)置僅對多結(jié)果集的情況適用,它將列出語句執(zhí)行后返回的結(jié)果集并每個結(jié)果集給一個名稱,名稱是逗號分隔的。 |
數(shù)據(jù)變更語句 insert,update 和 delete 的實現(xiàn)非常接近:
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
Insert, Update 和 Delete 的屬性
屬性 | 描述 |
---|---|
id | 命名空間中的唯一標(biāo)識符,可被用來代表這條語句。 |
parameterType | 將要傳入語句的參數(shù)的完全限定類名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的參數(shù),默認(rèn)值為 unset。 |
flushCache | 將其設(shè)置為 true,任何時候只要語句被調(diào)用,都會導(dǎo)致本地緩存和二級緩存都會被清空,默認(rèn)值:true(對應(yīng)插入、更新和刪除語句)。 |
timeout | 這個設(shè)置是在拋出異常之前,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)。默認(rèn)值為 unset(依賴驅(qū)動)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認(rèn)值:PREPARED。 |
useGeneratedKeys | (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數(shù)據(jù)庫內(nèi)部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關(guān)系數(shù)據(jù)庫管理系統(tǒng)的自動遞增字段),默認(rèn)值:false。 |
keyProperty | (僅對 insert 和 update 有用)唯一標(biāo)記一個屬性,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設(shè)置它的鍵值,默認(rèn):unset。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
keyColumn | (僅對 insert 和 update 有用)通過生成的鍵值設(shè)置表中的列名,這個設(shè)置僅在某些數(shù)據(jù)庫(像 PostgreSQL)是必須的,當(dāng)主鍵列不是表中的第一列的時候需要設(shè)置。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
databaseId | 如果配置了 databaseIdProvider,MyBatis 會加載所有的不帶 databaseId 或匹配當(dāng)前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
下面就是 insert,update 和 delete 語句的示例:
<insert id="insertAuthor">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>
如前所述,插入語句的配置規(guī)則更加豐富,在插入語句里面有一些額外的屬性和子元素用來處理主鍵的生成,而且有多種生成方式。
首先,如果你的數(shù)據(jù)庫支持自動生成主鍵的字段(比如 MySQL 和 SQL Server),那么你可以設(shè)置 useGeneratedKeys=”true”,然后再把 keyProperty 設(shè)置到目標(biāo)屬性上就OK了。例如,如果上面的 Author 表已經(jīng)對 id 使用了自動生成的列類型,那么語句可以修改為:
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
如果你的數(shù)據(jù)庫還支持多行插入, 你也可以傳入一個Authors數(shù)組或集合,并返回自動生成的主鍵。
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id"> insert into Author (username, password, email, bio) values <foreach item="item" collection="list" separator=","> (#{item.username}, #{item.password}, #{item.email}, #{item.bio}) </foreach> </insert>
在上面的示例中,selectKey 元素將會首先運行,Author 的 id 會被設(shè)置,然后插入語句會被調(diào)用。這給你了一個和數(shù)據(jù)庫中來處理自動生成的主鍵類似的行為,避免了使 Java 代碼變得復(fù)雜。
selectKey 元素描述如下:
<selectKey
keyProperty="id"
resultType="int"
order="BEFORE"
statementType="PREPARED">
selectKey 的屬性
屬性 | 描述 |
---|---|
keyProperty | selectKey 語句結(jié)果應(yīng)該被設(shè)置的目標(biāo)屬性。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
keyColumn | 匹配屬性的返回結(jié)果集中的列名稱。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
resultType | 結(jié)果的類型。MyBatis 通??梢酝扑愠鰜?,但是為了更加確定寫上也不會有什么問題。MyBatis 允許任何簡單類型用作主鍵的類型,包括字符串。如果希望作用于多個生成的列,則可以使用一個包含期望屬性的 Object 或一個 Map。 |
order | 這可以被設(shè)置為 BEFORE 或 AFTER。如果設(shè)置為 BEFORE,那么它會首先選擇主鍵,設(shè)置 keyProperty 然后執(zhí)行插入語句。如果設(shè)置為 AFTER,那么先執(zhí)行插入語句,然后是 selectKey 元素 - 這和像 Oracle 的數(shù)據(jù)庫相似,在插入語句內(nèi)部可能有嵌入索引調(diào)用。 |
statementType | 與前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 語句的映射類型,分別代表 PreparedStatement 和 CallableStatement 類型。 |
這個元素可以被用來定義可重用的 SQL 代碼段,可以包含在其他語句中。它可以靜態(tài)地(在加載階段)參數(shù)化。不同的屬性值可以在include實例中有所不同。 比如:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
這個 SQL 片段可以被包含在其他語句中,例如:
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
屬性值也可以用于 include refid 屬性或 include 子句中的屬性值,例如:
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
前面的所有語句中你所見到的都是簡單參數(shù)的例子,實際上參數(shù)是 MyBatis 非常強(qiáng)大的元素,對于簡單的做法,大概 90% 的情況參數(shù)都很少,比如:
<select id="selectUsers" resultType="User">
select id, username, password
from users
where id = #{id}
</select>
上面的這個示例說明了一個非常簡單的命名參數(shù)映射。參數(shù)類型被設(shè)置為 User,這樣這個參數(shù)就可以被設(shè)置成任何內(nèi)容。原生的類型或簡單數(shù)據(jù)類型(比如整型和字符串)因為沒有相關(guān)屬性,它會完全用參數(shù)值來替代。然而,如果傳入一個復(fù)雜的對象,行為就會有一點不同了。比如:
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果 User 類型的參數(shù)對象傳遞到了語句中,id、username 和 password 屬性將會被查找,然后將它們的值傳入預(yù)處理語句的參數(shù)中。
這點對于向語句中傳參是比較好的而且又簡單,不過參數(shù)映射的功能遠(yuǎn)不止于此。
首先,像 MyBatis 的其他部分一樣,參數(shù)也可以指定一個特殊的數(shù)據(jù)類型。
#{property,javaType=int,jdbcType=NUMERIC}
像 MyBatis 的剩余部分一樣,javaType 通??梢詮膮?shù)對象中來去確定,前提是只要對象不是一個 HashMap。那么 javaType 應(yīng)該被確定來保證使用正確類型處理器。
NOTE 如果 null 被當(dāng)作值來傳遞,對于所有可能為空的列,JDBC Type 是需要的。你可以自己通過閱讀預(yù)處理語句的 setNull() 方法的 JavaDocs 文檔來研究這種情況。
為了以后定制類型處理方式,你也可以指定一個特殊的類型處理器類(或別名),比如:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
盡管看起來配置變得越來越繁瑣,但實際上是很少去設(shè)置它們。
對于數(shù)值類型,還有一個小數(shù)保留位數(shù)的設(shè)置,來確定小數(shù)點后保留的位數(shù)。
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
最后,mode 屬性允許你指定 IN,OUT 或 INOUT 參數(shù)。如果參數(shù)為 OUT 或 INOUT,參數(shù)對象屬性的真實值將會被改變,就像你在獲取輸出參數(shù)時所期望的那樣。如果 mode 為 OUT(或 INOUT),而且 jdbcType 為 CURSOR(也就是 Oracle 的 REFCURSOR),你必須指定一個 resultMap 來映射結(jié)果集到參數(shù)類型。要注意這里的 javaType 屬性是可選的,如果左邊的空白是 jdbcType 的 CURSOR 類型,它會自動地被設(shè)置為結(jié)果集。
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
MyBatis 也支持很多高級的數(shù)據(jù)類型,比如結(jié)構(gòu)體,但是當(dāng)注冊 out 參數(shù)時你必須告訴它語句類型名稱。比如(再次提示,在實際中要像這樣不能換行):
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
盡管所有這些強(qiáng)大的選項很多時候你只簡單指定屬性名,其他的事情 MyBatis 會自己去推斷,最多你需要為可能為空的列名指定 jdbcType
。
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
默認(rèn)情況下,使用#{}格式的語法會導(dǎo)致 MyBatis 創(chuàng)建預(yù)處理語句屬性并安全地設(shè)置值(比如?)。這樣做更安全,更迅速,通常也是首選做法,不過有時你只是想直接在 SQL 語句中插入一個不改變的字符串。比如,像 ORDER BY,你可以這樣來使用:
ORDER BY ${columnName}
這里 MyBatis 不會修改或轉(zhuǎn)義字符串。
NOTE 以這種方式接受從用戶輸出的內(nèi)容并提供給語句中不變的字符串是不安全的,會導(dǎo)致潛在的 SQL 注入攻擊,因此要么不允許用戶輸入這些字段,要么自行轉(zhuǎn)義并檢驗。
resultMap 元素是 MyBatis 中最重要最強(qiáng)大的元素。它就是讓你遠(yuǎn)離 90%的需要從結(jié)果 集中取出數(shù)據(jù)的 JDBC 代碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支持的事 情。 事實上, 編寫相似于對復(fù)雜語句聯(lián)合映射這些等同的代碼, 也許可以跨過上千行的代碼。 ResultMap 的設(shè)計就是簡單語句不需要明確的結(jié)果映射,而很多復(fù)雜語句確實需要描述它們 的關(guān)系。
你已經(jīng)看到簡單映射語句的示例了,但沒有明確的 resultMap。比如:
<select id="selectUsers" resultType="map">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
這樣一個語句簡單作用于所有列被自動映射到 HashMap 的鍵上,這由 ?resultType
? 屬性 指定。這在很多情況下是有用的,但是 HashMap 不能很好描述一個領(lǐng)域模型。那樣你的應(yīng) 用程序?qū)褂?JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 對象)來作為領(lǐng)域 模型。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 可以被映射到結(jié)果集,就像映射到 HashMap 一樣簡單。
<select id="selectUsers" resultType="com.someapp.model.User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
要記住類型別名是你的伙伴。使用它們你可以不用輸入類的全路徑。比如:
<!-- In mybatis-config.xml file -->
<typeAlias type="com.someapp.model.User" alias="User"/>
<!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
這些情況下,MyBatis 會在幕后自動創(chuàng)建一個 ResultMap,基于屬性名來映射列到 JavaBean 的屬性上。如果列名沒有精確匹配,你可以在列名上使用 select 字句的別名(一個 基本的 SQL 特性)來匹配標(biāo)簽。比如:
<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>
ResultMap 最優(yōu)秀的地方你已經(jīng)了解了很多了,但是你還沒有真正的看到一個。這些簡 單的示例不需要比你看到的更多東西。 只是出于示例的原因, 讓我們來看看最后一個示例中 外部的 resultMap 是什么樣子的,這也是解決列名不匹配的另外一種方式。
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="username"/>
<result property="password" column="password"/>
</resultMap>
引用它的語句使用 resultMap 屬性就行了(注意我們?nèi)サ袅?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ù)庫不用永遠(yuǎn)是你想要的或需要它們是什么樣的。而我們 最喜歡的數(shù)據(jù)庫最好是第三范式或 BCNF 模式,但它們有時不是。如果可能有一個單獨的 數(shù)據(jù)庫映射,所有應(yīng)用程序都可以使用它,這是非常好的,但有時也不是。結(jié)果映射就是 MyBatis 提供處理這個問題的答案。
比如,我們?nèi)绾斡成湎旅孢@個語句?
<!-- Very Complex Statement -->
<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>
你可能想把它映射到一個智能的對象模型,包含一個作者寫的博客,有很多的博文,每 篇博文有零條或多條的評論和標(biāo)簽。 下面是一個完整的復(fù)雜結(jié)果映射例子 (假設(shè)作者, 博客, 博文, 評論和標(biāo)簽都是類型的別名) 我們來看看, 。 但是不用緊張, 我們會一步一步來說明。 當(dāng)天最初它看起來令人生畏,但實際上非常簡單。
<!-- Very Complex Result Map -->
<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 元素有很多子元素和一個值得討論的結(jié)構(gòu)。 下面是 resultMap 元素的概念視圖
constructor
- 類在實例化時,用來注入結(jié)果到構(gòu)造方法中
idArg
- ID 參數(shù);標(biāo)記結(jié)果作為 ID 可以幫助提高整體效能arg
- 注入到構(gòu)造方法的一個普通結(jié)果id
– 一個 ID 結(jié)果;標(biāo)記結(jié)果作為 ID 可以幫助提高整體效能result
– 注入到字段或 JavaBean 屬性的普通結(jié)果association
– 一個復(fù)雜的類型關(guān)聯(lián);許多結(jié)果將包成這種類型
collection
– 復(fù)雜類型的集
discriminator
– 使用結(jié)果值來決定使用哪個結(jié)果映射
case
– 基于某些值的結(jié)果映射
ResultMap Attributes
屬性 | 描述 |
---|---|
id
|
此名稱空間中的唯一標(biāo)識符,可用于引用此結(jié)果映射。 |
type
|
一個完全特定的 Java 類名,或者一個類型別名(參見上表中的內(nèi)置類型別名列表)。 |
autoMapping
|
如果存在,MyBatis 將啟用或禁用這個 ResultMap 的自動操作。此屬性覆蓋全局?autoMappingBehavior ?。默認(rèn)值:未設(shè)置的。 |
最佳實踐 通常逐步建立結(jié)果映射。單元測試的真正幫助在這里。如果你嘗試創(chuàng)建 一次創(chuàng)建一個向上面示例那樣的巨大的結(jié)果映射, 那么可能會有錯誤而且很難去控制它 來工作。開始簡單一些,一步一步的發(fā)展。而且要進(jìn)行單元測試!使用該框架的缺點是 它們有時是黑盒(是否可見源代碼) 。你確定你實現(xiàn)想要的行為的最好選擇是編寫單元 測試。它也可以你幫助得到提交時的錯誤。
下面一部分將詳細(xì)說明每個元素。
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
這些是結(jié)果映射最基本內(nèi)容。id 和 result 都映射一個單獨列的值到簡單數(shù)據(jù)類型(字符 串,整型,雙精度浮點數(shù),日期等)的單獨屬性或字段。
這兩者之間的唯一不同是 id 表示的結(jié)果將是當(dāng)比較對象實例時用到的標(biāo)識屬性。這幫 助來改進(jìn)整體表現(xiàn),特別是緩存和嵌入結(jié)果映射(也就是聯(lián)合映射) 。
每個都有一些屬性:
?Id and Result Attributes
?
屬性 | 描述 |
---|---|
property
|
映射到列結(jié)果的字段或?qū)傩浴H绻ヅ涞氖谴嬖诘?和給定名稱相同 的 JavaBeans 的屬性,那么就會使用。否則 MyBatis 將會尋找給定名稱 property 的字段。這兩種情形你可以使用通常點式的復(fù)雜屬性導(dǎo)航。比如,你 可以這樣映射一些東西: ?"username" ?,或者映射到一些復(fù)雜的東西: ?"address.street.number" ? 。 |
column
|
從數(shù)據(jù)庫中得到的列名,或者是列名的重命名標(biāo)簽。這也是通常和會 傳遞給 ?resultSet.getString(columnName) ?方法參數(shù)中相同的字符串。 |
javaType
|
一個 Java 類的完全限定名,或一個類型別名(參考上面內(nèi)建類型別名 的列表) 。如果你映射到一個 JavaBean,MyBatis 通??梢詳喽愋汀?然而,如果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來保證所需的行為。 |
jdbcType
|
在這個表格之后的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅 僅需要對插入,更新和刪除操作可能為空的列進(jìn)行處理。這是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定 這個類型-但僅僅對可能為空的值。 |
typeHandler
|
我們在前面討論過默認(rèn)的類型處理器。使用這個屬性,你可以覆蓋默 認(rèn)的類型處理器。這個屬性值是類的完全限定名或者是一個類型處理 器的實現(xiàn),或者是類型別名。 |
為了未來的參考,MyBatis 通過包含的 jdbcType 枚舉型,支持下面的 JDBC 類型。
BIT
|
FLOAT
|
CHAR
|
TIMESTAMP
|
OTHER
|
UNDEFINED
|
---|---|---|---|---|---|
TINYINT
|
REAL
|
VARCHAR
|
BINARY
|
BLOG
|
NVARCHAR
|
SMALLINT
|
DOUBLE
|
LONGVARCHAR
|
VARBINARY
|
CLOB
|
NCHAR
|
INTEGER
|
NUMERIC
|
DATE
|
LONGVARBINARY
|
BOOLEAN
|
NCLOB
|
BIGINT
|
DECIMAL
|
TIME
|
NULL
|
CURSOR
|
ARRAY
|
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
</constructor>
對于大多數(shù)數(shù)據(jù)傳輸對象(Data Transfer Object,DTO)類型,屬性可以起作用,而且像 你絕大多數(shù)的領(lǐng)域模型, 指令也許是你想使用一成不變的類的地方。 通常包含引用或查詢數(shù) 據(jù)的表很少或基本不變的話對一成不變的類來說是合適的。 構(gòu)造方法注入允許你在初始化時 為類設(shè)置屬性的值,而不用暴露出公有方法。MyBatis 也支持私有屬性和私有 JavaBeans 屬 性來達(dá)到這個目的,但是一些人更青睞構(gòu)造方法注入。構(gòu)造方法元素支持這個。
看看下面這個構(gòu)造方法:
public class User {
//...
public User(int id, String username) {
//...
}
//...
}
為了向這個構(gòu)造方法中注入結(jié)果,MyBatis 需要通過它的參數(shù)的類型來標(biāo)識構(gòu)造方法。 Java 沒有自查(反射)參數(shù)名的方法。所以當(dāng)創(chuàng)建一個構(gòu)造方法元素時,保證參數(shù)是按順序 排列的,而且數(shù)據(jù)類型也是確定的。
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
</constructor>
剩余的屬性和規(guī)則和固定的 id 和 result 元素是相同的。
屬性 | 描述 |
---|---|
column
|
來自數(shù)據(jù)庫的類名,或重命名的列標(biāo)簽。這和通常傳遞給 ?resultSet.getString(columnName) ?方法的字符串是相同的。 |
javaType
|
一個 Java 類的完全限定名,或一個類型別名(參考上面內(nèi)建類型別名的列表)。 如果你映射到一個 JavaBean,MyBatis 通??梢詳喽愋?。然而,如 果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來保證所需的 行為。 |
jdbcType
|
在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進(jìn)行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。 |
typeHandler
|
我們在前面討論過默認(rèn)的類型處理器。使用這個屬性,你可以覆蓋默認(rèn)的 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實現(xiàn), 或者是類型別名。 |
select
|
另一個映射語句的 ID,該語句將加載此屬性映射所需的復(fù)雜類型。從列屬性中指定的列檢索的值將作為參數(shù)傳遞給目標(biāo) select 語句。更多信息請參見關(guān)聯(lián)元素。 |
resultMap
|
這是 ResultMap 的 ID,它可以將此參數(shù)的嵌套結(jié)果映射到適當(dāng)?shù)膶ο髨D中。這是對另一個 select 語句調(diào)用的替代方法。它允許您將多個表連接到一個結(jié)果集中。這樣的ResultSet 將包含重復(fù)的、重復(fù)的數(shù)據(jù)組,需要對這些數(shù)據(jù)進(jìn)行分解并正確地映射到嵌套的對象圖中。為了實現(xiàn)這一點,MyBatis 允許將結(jié)果映射“鏈”在一起,以處理嵌套的結(jié)果。更多信息請參見下面的關(guān)聯(lián)元素。 |
關(guān)聯(lián)
<association property="author" column="blog_author_id" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
關(guān)聯(lián)元素處理"有一個"類型的關(guān)系。比如,在我們的示例中,一個博客有一個用戶。 關(guān)聯(lián)映射就工作于這種結(jié)果之上。你指定了目標(biāo)屬性,來獲取值的列,屬性的 java 類型(很 多情況下 MyBatis 可以自己算出來) ,如果需要的話還有 jdbc 類型,如果你想覆蓋或獲取的 結(jié)果值還需要類型控制器。
關(guān)聯(lián)中不同的是你需要告訴 MyBatis 如何加載關(guān)聯(lián)。MyBatis 在這方面會有兩種不同的 方式:
resultMap 屬性的結(jié)果映射不同。
屬性 | 描述 |
---|---|
property
|
映射到列結(jié)果的字段或?qū)傩?。如果匹配的是存在?和給定名稱相同的 property JavaBeans 的屬性, 那么就會使用。 否則 MyBatis 將會尋找給定名稱的字段。 這兩種情形你可以使用通常點式的復(fù)雜屬性導(dǎo)航。比如,你可以這樣映射 一 些 東 西 :?" username " ?, 或 者 映 射 到 一 些 復(fù) 雜 的 東 西 : ?"address.street.number" ? 。 |
javaType
|
一個 Java 類的完全限定名,或一個類型別名(參考上面內(nèi)建類型別名的列 表) 。如果你映射到一個 JavaBean,MyBatis 通??梢詳喽愋?。然而,如 javaType 果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來保證所需的 行為。 |
jdbcType
|
在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進(jìn)行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。 |
typeHandler
|
我們在前面討論過默認(rèn)的類型處理器。使用這個屬性,你可以覆蓋默認(rèn)的 typeHandler 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實現(xiàn), 或者是類型別名。 |
屬性 | 描述 |
---|---|
column
|
來自數(shù)據(jù)庫的類名,或重命名的列標(biāo)簽。這和通常傳遞給 ?resultSet.getString(columnName) ?方法的字符串是相同的。 column 注 意 : 要 處 理 復(fù) 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過? column= " {prop1=col1,prop2=col2} " ? 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數(shù)對象形式來設(shè)置給目標(biāo)嵌套查詢語句。 |
select
|
另外一個映射語句的 ID,可以加載這個屬性映射需要的復(fù)雜類型。獲取的 在列屬性中指定的列的值將被傳遞給目標(biāo) select 語句作為參數(shù)。表格后面 有一個詳細(xì)的示例。 select 注 意 : 要 處 理 復(fù) 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 ?column= " {prop1=col1,prop2=col2} " ?這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數(shù)對象形式來設(shè)置給目標(biāo)嵌套查詢語句。 |
fetchType
|
可選的。有效值是 lazy 的和 eager 的。如果存在,它將替代此映射的全局配置參數(shù)? lazyLoadingEnabled ?。 |
示例:
<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>
我們有兩個查詢語句:一個來加載博客,另外一個來加載作者,而且博客的結(jié)果映射描 述了"selectAuthor"語句應(yīng)該被用來加載它的 author 屬性。
其他所有的屬性將會被自動加載,假設(shè)它們的列和屬性名相匹配。
這種方式很簡單, 但是對于大型數(shù)據(jù)集合和列表將不會表現(xiàn)很好。 問題就是我們熟知的 "N+1 查詢問題"。概括地講,N+1 查詢問題可以是這樣引起的:
這個問題會導(dǎo)致成百上千的 SQL 語句被執(zhí)行。這通常不是期望的。
MyBatis 能延遲加載這樣的查詢就是一個好處,因此你可以分散這些語句同時運行的消 耗。然而,如果你加載一個列表,之后迅速迭代來訪問嵌套的數(shù)據(jù),你會調(diào)用所有的延遲加 載,這樣的行為可能是很糟糕的。
所以還有另外一種方法。
屬性 | 描述 |
---|---|
resultMap
|
這是結(jié)果映射的 ID,可以映射關(guān)聯(lián)的嵌套結(jié)果到一個合適的對象圖中。這 是一種替代方法來調(diào)用另外一個查詢語句。這允許你聯(lián)合多個表來合成到 ?resultMap ? 一個單獨的結(jié)果集。這樣的結(jié)果集可能包含重復(fù),數(shù)據(jù)的重復(fù)組需要被分 解,合理映射到一個嵌套的對象圖。為了使它變得容易,MyBatis 讓你"鏈 接"結(jié)果映射,來處理嵌套結(jié)果。一個例子會很容易來仿照,這個表格后 面也有一個示例。 |
columnPrefix
|
在連接多個表時,必須使用列別名以避免結(jié)果集中的重復(fù)列名。指定 ?columnPrefix ? 允許您將這些列映射到外部?resultMap ?。請參閱本節(jié)后面解釋的示例。 |
notNullColumn
|
默認(rèn)情況下,只有在映射到子對象屬性的至少一個列非空時,才會創(chuàng)建子對象。有了這個屬性,您可以通過指定哪些列必須有一個值來改變這種行為,這樣 MyBatis 將只在這些列中的任何一列不為空時創(chuàng)建子對象??梢允褂枚禾栕鳛榉指舴付ǘ鄠€列名。默認(rèn)值:未設(shè)置的。 |
autoMapping
|
如果存在,MyBatis 將在將結(jié)果映射到此屬性時啟用或禁用自動映射。此屬性覆蓋全局 ?autoMappingBehavior ?。注意,它對外部? resultMap ? 沒有影響,因此與?select ?或 ?resultMap ? 屬性一起使用是沒有意義的。默認(rèn)值:未設(shè)置的。 |
在上面你已經(jīng)看到了一個非常復(fù)雜的嵌套關(guān)聯(lián)的示例。 下面這個是一個非常簡單的示例 來說明它如何工作。代替了執(zhí)行一個分離的語句,我們聯(lián)合博客表和作者表在一起,就像:
<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>
注意這個聯(lián)合查詢, 以及采取保護(hù)來確保所有結(jié)果被唯一而且清晰的名字來重命名。 這使得映射非常簡單。現(xiàn)在我們可以映射這個結(jié)果:
<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>
在上面的示例中你可以看到博客的作者關(guān)聯(lián)代表著"authorResult"結(jié)果映射來加載作 者實例。
非常重要: 在嵌套據(jù)誒過映射中 id 元素扮演了非常重要的角色。應(yīng)應(yīng)該通常指定一個 或多個屬性,它們可以用來唯一標(biāo)識結(jié)果。實際上就是如果你離開她了,但是有一個嚴(yán)重的 性能問題時 MyBatis 仍然可以工作。選擇的屬性越少越好,它們可以唯一地標(biāo)識結(jié)果。主鍵 就是一個顯而易見的選擇(盡管是聯(lián)合主鍵)。
現(xiàn)在,上面的示例用了外部的結(jié)果映射元素來映射關(guān)聯(lián)。這使得 Author 結(jié)果映射可以 重用。然而,如果你不需要重用它的話,或者你僅僅引用你所有的結(jié)果映射合到一個單獨描 述的結(jié)果映射中。你可以嵌套結(jié)果映射。這里給出使用這種方式的相同示例:
<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語句如下:
<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>
回想一下,Author的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>
由于結(jié)果中的列名與resultMap中定義的列不同,因此您需要指定columnPrefix來重新使用resultMap,以便映射合Author的結(jié)果。
<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>
上面你已經(jīng)看到了如何處理"有一個"類型關(guān)聯(lián)。但是"有很多個"是怎樣的?下面這 個部分就是來討論這個主題的。
<collection property="posts" ofType="domain.blog.Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
集合元素的作用幾乎和關(guān)聯(lián)是相同的。實際上,它們也很相似,文檔的異同是多余的。 所以我們更多關(guān)注于它們的不同。
我們來繼續(xù)上面的示例,一個博客只有一個作者。但是博客有很多文章。在博客類中, 這可以由下面這樣的寫法來表示:
private List posts;
要映射嵌套結(jié)果集合到 List 中,我們使用集合元素。就像關(guān)聯(lián)元素一樣,我們可以從 連接中使用嵌套查詢,或者嵌套結(jié)果。
首先,讓我們看看使用嵌套查詢來為博客加載文章。
<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="Blog">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
這里你應(yīng)該注意很多東西,但大部分代碼和上面的關(guān)聯(lián)元素是非常相似的。首先,你應(yīng) 該注意我們使用的是集合元素。然后要注意那個新的"ofType"屬性。這個屬性用來區(qū)分 JavaBean(或字段)屬性類型和集合包含的類型來說是很重要的。所以你可以讀出下面這個 映射:
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
讀作: "在 Post 類型的 ArrayList 中的 posts 的集合。"
javaType 屬性是不需要的,因為 MyBatis 在很多情況下會為你算出來。所以你可以縮短 寫法:
<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
至此,你可以猜測集合的嵌套結(jié)果是如何來工作的,因為它和關(guān)聯(lián)完全相同,除了它應(yīng) 用了一個"ofType"屬性
First, let's look at the SQL:
<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>
我們又一次聯(lián)合了博客表和文章表,而且關(guān)注于保證特性,結(jié)果列標(biāo)簽的簡單映射?,F(xiàn) 在用文章映射集合映射博客,可以簡單寫為:
<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>
同樣,要記得 id 元素的重要性,如果你不記得了,請閱讀上面的關(guān)聯(lián)部分。
同樣, 如果你引用更長的形式允許你的結(jié)果映射的更多重用, 你可以使用下面這個替代 的映射:
<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>
注意 這個對你所映射的內(nèi)容沒有深度,廣度或關(guān)聯(lián)和集合相聯(lián)合的限制。當(dāng)映射它們 時你應(yīng)該在大腦中保留它們的表現(xiàn)。 你的應(yīng)用在找到最佳方法前要一直進(jìn)行的單元測試和性 能測試。好在 myBatis 讓你后來可以改變想法,而不對你的代碼造成很小(或任何)影響。
高級關(guān)聯(lián)和集合映射是一個深度的主題。文檔只能給你介紹到這了。加上一點聯(lián)系,你 會很快清楚它們的用法。
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
有時一個單獨的數(shù)據(jù)庫查詢也許返回很多不同 (但是希望有些關(guān)聯(lián)) 數(shù)據(jù)類型的結(jié)果集。 鑒別器元素就是被設(shè)計來處理這個情況的, 還有包括類的繼承層次結(jié)構(gòu)。 鑒別器非常容易理 解,因為它的表現(xiàn)很像 Java 語言中的 switch 語句。
定義鑒別器指定了 column 和 javaType 屬性。 列是 MyBatis 查找比較值的地方。 JavaType 是需要被用來保證等價測試的合適類型(盡管字符串在很多情形下都會有用)。比如:
<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" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>
在這個示例中, MyBatis 會從結(jié)果集中得到每條記錄, 然后比較它的 vehicle 類型的值。 如果它匹配任何一個鑒別器的實例,那么就使用這個實例指定的結(jié)果映射。換句話說,這樣 做完全是剩余的結(jié)果映射被忽略(除非它被擴(kuò)展,這在第二個示例中討論) 。如果沒有任何 一個實例相匹配,那么 MyBatis 僅僅使用鑒別器塊外定義的結(jié)果映射。所以,如果 carResult 按如下聲明:
<resultMap id="carResult" type="Car">
<result property="doorCount" column="door_count" />
</resultMap>
那么只有 doorCount 屬性會被加載。這步完成后完整地允許鑒別器實例的獨立組,盡管 和父結(jié)果映射可能沒有什么關(guān)系。這種情況下,我們當(dāng)然知道 cars 和 vehicles 之間有關(guān)系, 如 Car 是一個 Vehicle 實例。因此,我們想要剩余的屬性也被加載。我們設(shè)置的結(jié)果映射的 簡單改變?nèi)缦隆?/p>
<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>
現(xiàn)在 vehicleResult 和 carResult 的屬性都會被加載了。
盡管曾經(jīng)有些人會發(fā)現(xiàn)這個外部映射定義會多少有一些令人厭煩之處。 因此還有另外一 種語法來做簡潔的映射風(fēng)格。比如:
<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>
要記得 這些都是結(jié)果映射, 如果你不指定任何結(jié)果, 那么 MyBatis 將會為你自動匹配列 和屬性。所以這些例子中的大部分是很冗長的,而其實是不需要的。也就是說,很多數(shù)據(jù)庫 是很復(fù)雜的,我們不太可能對所有示例都能依靠它。
正如你在前面一節(jié)看到的,在簡單的場景下,MyBatis 可以替你自動映射查詢結(jié)果。 如果遇到復(fù)雜的場景,你需要構(gòu)建一個 result map。 但是在本節(jié)你將看到,你也可以混合使用這兩種策略。 讓我們到深一點的層面上看看自動映射是怎樣工作的。
當(dāng)自動映射查詢結(jié)果時,MyBatis 會獲取 sql 返回的列名并在 java 類中查找相同名字的屬性(忽略大小寫)。 這意味著如果 Mybatis 發(fā)現(xiàn)了 _ID_ 列和 _id_ 屬性,Mybatis會將_ID_ 的值賦給 id。
通常數(shù)據(jù)庫列使用大寫單詞命名,單詞間用下劃線分隔;而java屬性一般遵循駝峰命名法。 為了在這兩種命名方式之間啟用自動映射,需要將 mapUnderscoreToCamelCase
設(shè)置為 true。
自動映射甚至在特定的 result map下也能工作。在這種情況下,對于每一個 result map,所有的 ResultSet 提供的列, 如果沒有被手工映射,則將被自動映射。自動映射處理完畢后手工映射才會被處理。 在接下來的例子中, id 和 _userName_列將被自動映射, _hashedpassword 列將根據(jù)配置映射。
<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
- 禁用自動映射,只有手動映射屬性才會被設(shè)置。PARTIAL
- 將自動映射結(jié)果,除了那些嵌套結(jié)果映射(連接)內(nèi)的結(jié)果。FULL
- 自動映射一切默認(rèn)值是 PARTIAL
,這是有原因的。 使用 FULL 時,將在處理連接結(jié)果時執(zhí)行自動映射,并且連接會檢索同一行中的多個不同實體的數(shù)據(jù),因此可能會導(dǎo)致不需要的映射。 要了解風(fēng)險,請查看下面的示例:
<select id="selectBlog" resultMap="blogResult">
select
B.id,
B.title,
A.username,
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<association property="author" resultMap="authorResult"/>
</resultMap>
<resultMap id="authorResult" type="Author">
<result property="username" column="author_username"/>
</resultMap>
在結(jié)果中 Blog 和 Author 均將自動映射。但是注意 Author 有一個 id 屬性,在 ResultSet 中有一個列名為 id, 所以 Author 的 id 將被填充為 Blog 的 id,這不是你所期待的。所以需要謹(jǐn)慎使用 FULL。
通過添加 autoMapping 屬性可以忽略自動映射等級配置,你可以啟用或者禁用自動映射指定的 ResultMap。
<resultMap id="userResultMap" type="User" autoMapping="false"> <result property="password" column="hashed_password"/> </resultMap>
MyBatis 包含一個非常強(qiáng)大的查詢緩存特性,它可以非常方便地配置和定制。MyBatis 3 中的緩存實現(xiàn)的很多改進(jìn)都已經(jīng)實現(xiàn)了,使得它更加強(qiáng)大而且易于配置。
默認(rèn)情況下是沒有開啟緩存的,除了局部的 session 緩存,可以增強(qiáng)變現(xiàn)而且處理循環(huán) 依賴也是必須的。要開啟二級緩存,你需要在你的 SQL 映射文件中添加一行:
<cache/>
字面上看就是這樣。這個簡單語句的效果如下:
所有的這些屬性都可以通過緩存元素的屬性來修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個更高級的配置創(chuàng)建了一個 FIFO 緩存,并每隔 60 秒刷新,存數(shù)結(jié)果對象或列表的 512 個引用,而且返回的對象被認(rèn)為是只讀的,因此在不同線程中的調(diào)用者之間修改它們會 導(dǎo)致沖突。
可用的收回策略有:
LRU
– 最近最少使用的:移除最長時間不被使用的對象。FIFO
– 先進(jìn)先出:按對象進(jìn)入緩存的順序來移除它們。SOFT
– 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。WEAK
– 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。默認(rèn)的是 LRU。
flushInterval (刷新間隔)可以被設(shè)置為任意的正整數(shù),而且它們代表一個合理的毫秒 形式的時間段。默認(rèn)情況是不設(shè)置,也就是沒有刷新間隔,緩存僅僅調(diào)用語句時刷新。
size(引用數(shù)目)可以被設(shè)置為任意正整數(shù),要記住你緩存的對象數(shù)目和你運行環(huán)境的 可用內(nèi)存資源數(shù)目。默認(rèn)值是 1024。
readOnly (只讀)屬性可以被設(shè)置為 true 或 false。只讀的緩存會給所有調(diào)用者返回緩 存對象的相同實例。因此這些對象不能被修改。這提供了很重要的性能優(yōu)勢??勺x寫的緩存 會返回緩存對象的拷貝(通過序列化) 。這會慢一些,但是安全,因此默認(rèn)是 false。
除了這些自定義緩存的方式, 你也可以通過實現(xiàn)你自己的緩存或為其他第三方緩存方案 創(chuàng)建適配器來完全覆蓋緩存行為。
<cache type="com.domain.something.MyCustomCache"/>
這個示 例展 示了 如何 使用 一個 自定義 的緩 存實 現(xiàn)。type 屬 性指 定的 類必 須實現(xiàn) org.mybatis.cache.Cache 接口。這個接口是 MyBatis 框架中很多復(fù)雜的接口之一,但是簡單 給定它做什么就行。
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
要配置你的緩存, 簡單和公有的 JavaBeans 屬性來配置你的緩存實現(xiàn), 而且是通過 cache 元素來傳遞屬性, 比如, 下面代碼會在你的緩存實現(xiàn)中調(diào)用一個稱為 "setCacheFile(String file)" 的方法:
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
你可以使用所有簡單類型作為 JavaBeans 的屬性,MyBatis 會進(jìn)行轉(zhuǎn)換。
記得緩存配置和緩存實例是綁定在 SQL 映射文件的命名空間是很重要的。因此,所有 在相同命名空間的語句正如綁定的緩存一樣。 語句可以修改和緩存交互的方式, 或在語句的 語句的基礎(chǔ)上使用兩種簡單的屬性來完全排除它們。默認(rèn)情況下,語句可以這樣來配置:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
因為那些是默認(rèn)的,你明顯不能明確地以這種方式來配置一條語句。相反,如果你想改 變默認(rèn)的行為,只能設(shè)置 flushCache 和 useCache 屬性。比如,在一些情況下你也許想排除 從緩存中查詢特定語句結(jié)果,或者你也許想要一個查詢語句來刷新緩存。相似地,你也許有 一些更新語句依靠執(zhí)行而不需要刷新緩存。
回想一下上一節(jié)內(nèi)容, 這個特殊命名空間的唯一緩存會被使用或者刷新相同命名空間內(nèi) 的語句。也許將來的某個時候,你會想在命名空間中共享相同的緩存配置和實例。在這樣的 情況下你可以使用 cache-ref 元素來引用另外一個緩存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
更多建議: