3.XML映射文件
MyBatis 的真正強大在于它的映射語句,也是它的魔力所在娜膘。由于它的異常強大逊脯,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比竣贪,你會立即發(fā)現(xiàn)省掉了將近 95% 的代碼军洼。MyBatis 就是針對 SQL 構建的,并且比普通的方法做的更好演怎。
SQL 映射文件有很少的幾個頂級元素(按照它們應該被定義的順序):
- cache – 給定命名空間的緩存配置歉眷。
- cache-ref – 其他命名空間緩存配置的引用。
- resultMap – 是最復雜也是最強大的元素颤枪,用來描述如何從數(shù)據(jù)庫結果集中來加載對象汗捡。
- sql – 可被其他語句引用的可重用語句塊。
- insert – 映射插入語句
- update – 映射更新語句
- delete – 映射刪除語句
- select – 映射查詢語句
3.1 select
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
類似的JDBC代碼:
// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
需要很多單獨的 JDBC 的代碼來提取結果并將它們映射到對象實例中,這就是 MyBatis 節(jié)省你時間的地方扇住。我們需要深入了解參數(shù)和結果映射春缕,細節(jié)部分我們下面來了解。
select 元素有很多屬性允許你配置艘蹋,來決定每條語句的作用細節(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">
3.2 insert, update 和 delete
<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 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)冯键,那么你可以設置 useGeneratedKeys=”true”,然后再把 keyProperty 設置到目標屬性上就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ù)庫或可能不支持自動生成主鍵 JDBC 驅動來說蚯舱,MyBatis 有另外一種方法來生成主鍵改化。
這里有一個簡單(甚至很傻)的示例,它可以生成一個隨機 ID(你最好不要這么做枉昏,但這里展示了 MyBatis 處理問題的靈活性及其所關心的廣度):
<insert id="insertAuthor">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
insert into Author
(id, username, password, email,bio, favourite_section)
values
(#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>
在上面的示例中陈肛,selectKey 元素將會首先運行,Author 的 id 會被設置兄裂,然后插入語句會被調(diào)用句旱。這給你了一個和數(shù)據(jù)庫中來處理自動生成的主鍵類似的行為,避免了使 Java 代碼變得復雜懦窘。
selectKey 元素描述如下:
<selectKey
keyProperty="id"
resultType="int"
order="BEFORE"
statementType="PREPARED">
3.3 sql
這個元素可以被用來定義可重用的 SQL 代碼段前翎,可以包含在其他語句中。
<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>
<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>
3.4 參數(shù)
參數(shù)是 MyBatis 非常強大的元素港华,對于簡單的做法,大概 90% 的情況參數(shù)都很少午衰,比如:
<select id="selectUsers" resultType="User">
select id, username, password
from users
where id = #{id}
</select>
上面的這個示例說明了一個非常簡單的命名參數(shù)映射立宜。參數(shù)類型被設置為 int,這樣這個參數(shù)就可以被設置成任何內(nèi)容臊岸。原生的類型或簡單數(shù)據(jù)類型(比如整型和字符串)因為沒有相關屬性橙数,它會完全用參數(shù)值來替代。然而帅戒,如果傳入一個復雜的對象灯帮,行為就會有一點不同了崖技。比如:
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果 User 類型的參數(shù)對象傳遞到了語句中,id钟哥、username 和 password 屬性將會被查找迎献,然后將它們的值傳入預處理語句的參數(shù)中。
這點對于向語句中傳參是比較好的而且又簡單腻贰,不過參數(shù)映射的功能遠不止于此吁恍。
首先,像 MyBatis 的其他部分一樣播演,參數(shù)也可以指定一個特殊的數(shù)據(jù)類型冀瓦。
#{property,javaType=int,jdbcType=NUMERIC}
像 MyBatis 的剩余部分一樣,javaType 通承纯荆可以從參數(shù)對象中來去確定翼闽,前提是只要對象不是一個 HashMap。那么 javaType 應該被確定來保證使用正確類型處理器顶霞。
NOTE: 如果 null 被當作值來傳遞肄程,對于所有可能為空的列锣吼,JDBC Type 是需要的选浑。你可以自己通過閱讀預處理語句的 setNull() 方法的 JavaDocs 文檔來研究這種情況。
為了以后定制類型處理方式玄叠,你也可以指定一個特殊的類型處理器類(或別名)古徒,比如:
#{age,javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler}
盡管看起來配置變得越來越繁瑣,但實際上是很少去設置它們读恃。
對于數(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 來映射結果集到參數(shù)類型再悼。要注意這里的 javaType 屬性是可選的核畴,如果左邊的空白是 jdbcType 的 CURSOR 類型,它會自動地被設置為結果集冲九。
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
MyBatis 也支持很多高級的數(shù)據(jù)類型谤草,比如結構體,但是當注冊 out 參數(shù)時你必須告訴它語句類型名稱。比如(再次提示丑孩,在實際中要像這樣不能換行):
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
盡管所有這些強大的選項很多時候你只簡單指定屬性名泳炉,其他的事情 MyBatis 會自己去推斷,最多你需要為可能為空的列名指定 jdbcType嚎杨。
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
-
字符串替換
默認情況下,使用#{}格式的語法會導致 MyBatis 創(chuàng)建預處理語句屬性并安全地設置值(比如?)花鹅。這樣做更安全,更迅速枫浙,通常也是首選做法刨肃,不過有時你只是想直接在 SQL 語句中插入一個不改變的字符串。比如箩帚,像 ORDER BY真友,你可以這樣來使用:
ORDER BY ${columnName}
這里 MyBatis 不會修改或轉義字符串。
NOTE: 以這種方式接受從用戶輸出的內(nèi)容并提供給語句中不變的字符串是不安全的紧帕,會導致潛在的 SQL 注入攻擊盔然,因此要么不允許用戶輸入這些字段,要么自行轉義并檢驗是嗜。
3.5 Result Maps
resultMap 元素是 MyBatis 中最重要最強大的元素愈案。它就是讓你遠離 90%的需要從結果 集中取出數(shù)據(jù)的 JDBC 代碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支持的事 情。 事實上, 編寫相似于對復雜語句聯(lián)合映射這些等同的代碼, 也許可以跨過上千行的代碼鹅搪。 ResultMap 的設計就是簡單語句不需要明確的結果映射,而很多復雜語句確實需要描述它們 的關系站绪。
使用 JavaBeans 或 POJOs(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 可以被映射到結果集,就像映射到 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 特性)來匹配標簽。比如:
<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>
3.5.1 ResultMap的高級功能
- constructor - 類在實例化時,用來注入結果到構造方法中
idArg - ID 參數(shù);標記結果作為 ID 可以幫助提高整體效能
arg - 注入到構造方法的一個普通結果 - id – 一個 ID 結果;標記結果作為 ID 可以幫助提高整體效能
- result – 注入到字段或 JavaBean 屬性的普通結果
- constructor
- association – 一個復雜的類型關聯(lián);許多結果將包成這種類型
嵌入結果映射 – 結果映射自身的關聯(lián),或者參考一個 - collection – 復雜類型的集
嵌入結果映射 – 結果映射自身的集,或者參考一個 - discriminator – 使用結果值來決定使用哪個結果映射
case – 基于某些值的結果映射
嵌入結果映射 – 這種情形結果也映射它本身,因此可以包含很多相 同的元素,或者它可以參照一個外部的結果映射 - 自動映射
正如你在前面一節(jié)看到的队萤,在簡單的場景下,MyBatis可以替你自動映射查詢結果矫钓。 如果遇到復雜的場景要尔,你需要構建一個result map舍杜。 但是在本節(jié)你將看到,你也可以混合使用這兩種策略赵辕。 讓我們到深一點的層面上看看自動映射是怎樣工作的既绩。
當自動映射查詢結果時,MyBatis會獲取sql返回的列名并在java類中查找相同名字的屬性(忽略大小寫)还惠。 這意味著如果Mybatis發(fā)現(xiàn)了_ID_列和_id_屬性饲握,Mybatis會將_ID_的值賦給id。
通常數(shù)據(jù)庫列使用大寫單詞命名蚕键,單詞間用下劃線分隔救欧;而java屬性一般遵循駝峰命名法。 為了在這兩種命名方式之間啟用自動映射锣光,需要將 mapUnderscoreToCamelCase設置為true笆怠。
自動映射甚至在特定的result map下也能工作。在這種情況下誊爹,對于每一個result map,所有的ResultSet提供的列蹬刷, 如果沒有被手工映射,則將被自動映射频丘。自動映射處理完畢后手工映射才會被處理办成。 在接下來的例子中, id 和 _userName_列將被自動映射椎镣, _hashedpassword 列將根據(jù)配置映射
3.6 cache
MyBatis 包含一個非常強大的查詢緩存特性,它可以非常方便地配置和定制诈火。MyBatis 3 中的緩存實現(xiàn)的很多改進都已經(jīng)實現(xiàn)了,使得它更加強大而且易于配置兽赁。
默認情況下是沒有開啟緩存的,除了局部的 session 緩存状答。要開啟二級緩存,你需要在你的 SQL 映射文件中添加一行:
<cache/>
字面上看就是這樣。這個簡單語句的效果如下:
- 映射語句文件中的所有 select 語句將會被緩存刀崖。
- 映射語句文件中的所有 insert,update 和 delete 語句會刷新緩存惊科。
- 緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回。
- 根據(jù)時間表(比如 no Flush Interval,沒有刷新間隔), 緩存不會以任何時間順序 來刷新亮钦。
- 緩存會存儲列表集合或對象(無論查詢方法返回什么)的 1024 個引用馆截。
- 緩存會被視為是 read/write(可讀/可寫)的緩存,意味著對象檢索不是共享的,而 且可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。
所有的這些屬性都可以通過緩存元素的屬性來修改蜂莉。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個更高級的配置創(chuàng)建了一個 FIFO 緩存,并每隔 60 秒刷新,存數(shù)結果對象或列表的 512 個引用,而且返回的對象被認為是只讀的,因此在不同線程中的調(diào)用者之間修改它們會 導致沖突蜡娶。
可用的收回策略有:
- LRU – 最近最少使用的:移除最長時間不被使用的對象。
- FIFO – 先進先出:按對象進入緩存的順序來移除它們映穗。
- SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象窖张。
- WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。
默認的是 LRU蚁滋。
除了這些自定義緩存的方式, 你也可以通過實現(xiàn)你自己的緩存或為其他第三方緩存方案 創(chuàng)建適配器來完全覆蓋緩存行為宿接。
<cache type="com.domain.something.MyCustomCache"/>
3.7 cache-ref
也許將來的某個時候,你會想在命名空間中共享相同的緩存配置和實例赘淮。在這樣的 情況下你可以使用 cache-ref 元素來引用另外一個緩存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>