MyBatis 的真正強大在於它的對映語句,也是它的魔力所在。由於它的異常強大,對映器的 XML 檔案就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 程式碼進行對比,你會立即發現省掉了將近 95% 的程式碼。MyBatis 就是針對 SQL 構建的,並且比普通的方法做的更好。
SQL 對映檔案有很少的幾個頂級元素(按照它們應該被定義的順序):
- cache – 給定名稱空間的快取配置。
- cache-ref – 其他名稱空間快取配置的引用。
- resultMap – 是最複雜也是最強大的元素,用來描述如何從資料庫結果集中來載入物件。
- sql – 可被其他語句引用的可重用語句塊。
- insert – 對映插入語句
- update – 對映更新語句
- delete – 對映刪除語句
- select – 對映查詢語句
- select(查詢)
查詢語句是 MyBatis 中最常用的元素之一,光能把資料存到資料庫中價值並不大,如果還能重新取出來才有用,多數應用也都是查詢比修改要頻繁。對每個插入、更新或刪除操作,通常對應多個查詢操作。這是 MyBatis 的基本原則之一,也是將焦點和努力放到查詢和結果對映的原因。簡單查詢的 select 元素是非常簡單的。比如:
<select
id="selectPerson"
parameterType="int"
resultType="hashmap">SELECT * FROM PERSON WHERE ID = #{id}
</select>
這個語句被稱作 selectPerson,接受一個 int(或 Integer)型別的引數,並返回一個 HashMap 型別的物件,其中的鍵是列名,值便是結果行中的對應值。
注意引數符號:
#{id}
這就告訴 MyBatis 建立一個預處理語句引數,通過 JDBC,這樣的一個引數在 SQL 中會由一個"?"來標識,並被傳遞到一個新的預處理語句中,就像這樣:
// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
當然,這需要很多單獨的 JDBC 的程式碼來提取結果並將它們對映到物件例項中,這就是 MyBatis 節省你時間的地方。我們需要深入瞭解引數和結果對映,細節部分我們下面來了解,select 元素有很多屬性允許你配置,來決定每條語句的作用細節:
屬性 |
描述 |
id |
在名稱空間中唯一的識別符號,可以被用來引用這條語句。 |
parameterType |
將會傳入這條語句的引數類的完全限定名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的引數,預設值為 unset。 |
resultType |
從這條語句中返回的期望型別的類的完全限定名或別名。注意如果是集合情形,那應該是集合可以包含的型別,而不能是集合本身。使用 resultType 或 resultMap,但不能同時使用。 |
resultMap |
外部 resultMap 的命名引用。結果集的對映是 MyBatis 最強大的特性,對其有一個很好的理解的話,許多複雜對映的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同時使用。 |
flushCache |
將其設定為 true,任何時候只要語句被呼叫,都會導致本地快取和二級快取都會被清空,預設值:false。 |
useCache |
將其設定為 true,將會導致本條語句的結果被二級快取,預設值:對 select 元素為 true。 |
timeout |
這個設定是在丟擲異常之前,驅動程式等待資料庫返回請求結果的秒數。預設值為 unset(依賴驅動)。 |
fetchSize |
這是嘗試影響驅動程式每次批量返回的結果行數和這個設定值相等。預設值為 unset(依賴驅動)。 |
statementType |
STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。 |
resultSetType |
FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一個,預設值為 unset (依賴驅動)。 |
databaseId |
如果配置了 databaseIdProvider,MyBatis 會載入所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
resultOrdered |
這個設定僅針對巢狀結果 select 語句適用:如果為 true,就是假設包含了巢狀結果集或是分組了,這樣的話當返回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。這就使得在獲取巢狀的結果集的時候不至於導致記憶體不夠用。預設值:false。 |
resultSets |
這個設定僅對多結果集的情況適用,它將列出語句執行後返回的結果集並每個結果集給一個名稱,名稱是逗號分隔的。 |
- insert, update 和 delete
語句
資料變更語句 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">
屬性 |
描述 |
id |
名稱空間中的唯一識別符號,可被用來代表這條語句。 |
parameterType |
將要傳入語句的引數的完全限定類名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的引數,預設值為 unset。 |
flushCache |
將其設定為 true,任何時候只要語句被呼叫,都會導致本地快取和二級快取都會被清空,預設值:true(對應插入、更新和刪除語句)。 |
timeout |
這個設定是在丟擲異常之前,驅動程式等待資料庫返回請求結果的秒數。預設值為 unset(依賴驅動)。 |
statementType |
STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。 |
useGeneratedKeys |
(僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由資料庫內部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關係資料庫管理系統的自動遞增欄位),預設值:false。 |
keyProperty |
(僅對 insert 和 update 有用)唯一標記一個屬性,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設定它的鍵值,預設:unset。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
keyColumn |
(僅對 insert 和 update 有用)通過生成的鍵值設定表中的列名,這個設定僅在某些資料庫(像 PostgreSQL)是必須的,當主鍵列不是表中的第一列的時候需要設定。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
databaseId |
如果配置了 databaseIdProvider,MyBatis 會載入所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
首先,如果你的資料庫支援自動生成主鍵的欄位(比如 MySQL 和 SQL Server),那麼你可以設定 useGeneratedKeys="true",然後再把 keyProperty 設定到目標屬性上就OK了。例如,如果上面的 Author 表已經對 id 使用了自動生成的列型別,那麼語句可以修改為:
<insert
id="insertAuthor"
useGeneratedKeys="true"
keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
如果你的資料庫還支援多行插入, 你也可以傳入一個Authors陣列或集合,並返回自動生成的主鍵:
<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>
對於不支援自動生成型別的資料庫或可能不支援自動生成主鍵 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 會被設定,然後插入語句會被呼叫。這給你了一個和資料庫中來處理自動生成的主鍵類似的行為,避免了使 Java 程式碼變得複雜。 selectKey 元素描述如下:
屬性 |
描述 |
keyProperty |
selectKey 語句結果應該被設定的目標屬性。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
keyColumn |
匹配屬性的返回結果集中的列名稱。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
resultType |
結果的型別。MyBatis 通常可以推算出來,但是為了更加確定寫上也不會有什麼問題。MyBatis 允許任何簡單型別用作主鍵的型別,包括字串。如果希望作用於多個生成的列,則可以使用一個包含期望屬性的 Object 或一個 Map。 |
order |
這可以被設定為 BEFORE 或 AFTER。如果設定為 BEFORE,那麼它會首先選擇主鍵,設定 keyProperty 然後執行插入語句。如果設定為 AFTER,那麼先執行插入語句,然後是 selectKey 元素 - 這和像 Oracle 的資料庫相似,在插入語句內部可能有嵌入索引呼叫。 |
statementType |
與前面相同,MyBatis 支援 STATEMENT,PREPARED 和 CALLABLE 語句的對映型別,分別代表 PreparedStatement 和 CallableStatement 型別。 |
- 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>
屬性值可以用於包含的refid屬性或者包含的字句裡面的屬性值,例如:
<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>
- 引數(Parameters)
前面的所有語句中你所見到的都是簡單引數的例子,實際上引數是 MyBatis 非常強大的元素,對於簡單的做法,大概 90% 的情況引數都很少,比如:
<select
id="selectUsers"
resultType="User">select id, username, password
from users
where id = #{id}
</select>
上面的這個示例說明了一個非常簡單的命名引數對映。引數型別被設定為 int,這樣這個引數就可以被設定成任何內容。原生的型別或簡單資料型別(比如整型和字串)因為沒有相關屬性,它會完全用引數值來替代。然而,如果傳入一個複雜的物件,行為就會有一點不同了。比如:
<insert
id="insertUser"
parameterType="User">insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果 User 型別的引數物件傳遞到了語句中,id、username 和 password 屬性將會被查詢,然後將它們的值傳入預處理語句的引數中。
這點對於向語句中傳參是比較好的而且又簡單,不過引數對映的功能遠不止於此。首先,像 MyBatis 的其他部分一樣,引數也可以指定一個特殊的資料型別,配置示例:
#{property,javaType=int,jdbcType=NUMERIC}
像 MyBatis 的typeHandler部分一樣,javaType 通常可以從引數物件中來去確定,前提是隻要物件不是一個 HashMap。那麼 javaType 應該被確定來保證使用正確型別處理器。為了以後定製型別處理方式,你也可以指定一個特殊的型別處理器類(或別名),比如:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
對於數值型別,還有一個小數保留位數的設定,來確定小數點後保留的位數。
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
最後,mode 屬性允許你指定 IN,OUT 或 INOUT 引數。如果引數為 OUT 或 INOUT,引數物件屬性的真實值將會被改變,就像你在獲取輸出引數時所期望的那樣。如果 mode 為 OUT(或 INOUT),而且 jdbcType 為 CURSOR(也就是 Oracle 的 REFCURSOR),你必須指定一個 resultMap 來對映結果集到引數型別。要注意這裡的 javaType 屬性是可選的,如果左邊的空白是 jdbcType 的 CURSOR 型別,它會自動地被設定為結果集。 儘管所有這些強大的選項很多時候你只簡單指定屬性名,其他的事情 MyBatis 會自己去推斷,最多你需要為可能為空的列名指定 jdbcType。
- 字串替換
預設情況下,使用#{}格式的語法會導致 MyBatis 建立預處理語句屬性並安全地設定值(比如?)。這樣做更安全,更迅速,通常也是首選做法,不過有時你只是想直接在 SQL 語句中插入一個不改變的字串。比如,像 ORDER BY,你可以這樣來使用:
ORDER BY ${columnName}
這裡 MyBatis 不會修改或轉義字串,以這種方式接受從使用者輸出的內容並提供給語句中不變的字串是不安全的,會導致潛在的 SQL 注入攻擊,因此要麼不允許使用者輸入這些欄位,要麼自行轉義並檢驗。
- 多引數傳遞
MyBatis中的對映語句有一個parameterType屬性來制定輸入引數的型別。如果我們想給對映語句傳入多個引數的話,我們可以將所有的輸入引數放到HashMap中,將HashMap傳遞給對映語句。MyBatis 還提供了另外一種傳遞多個輸入引數給對映語句的方法。假設我們想通過給定的name和email資訊查詢學生資訊,定義查詢介面如下:
Public interface StudentMapper
{
List<Student> findAllStudentsByNameEmail(String name, String email);
}
MyBatis 支援將多個輸入引數傳遞給對映語句,並以#{param}的語法形式引用它們:
<select
id="findAllStudentsByNameEmail"
resultMap="StudentResult">select stud_id, name,email, phone from Students
where name=#{param1} and email=#{param2}
</select>
這裡#{param1}引用第一個引數name,而#{param2}引用了第二個引數email
還可以使用 @Param 註解來給引數命名,定義查詢介面如下:
Public interface StudentMapper
{
List<Student> findAllStudentsByNameEmail(@Param("name") String name, @Param("email") String email);
}
MyBatis 配置檔案可以直接使用命名的引數,如下配置:
<select
id="findAllStudentsByNameEmail"
resultMap="StudentResult">select stud_id, name,email, phone from Students
where name=#{name} and email=#{email}
</select>
- 儲存過程和輸入引數
儲存過程的呼叫必須設定 statementType="CALLABLE",並且儲存過程的呼叫語法為 { call proc_name} 如果有輸出引數,則還需要設定引數的 mode,示例如下:
- 儲存過程:
CREATE DEFINER=`dev`@`%` PROCEDURE `procBuild_BillNo`(in prefix varchar(8),in serial varchar(10),out billNo varchar(20))
BEGIN
SET billNo = CONCAT(prefix,serial);
END
- 方法定義:
void
BuildBillNo(HashMap<String,Object> map);注意:儲存過程的輸出引數,只能通過傳入的
HashMap
來獲取 - Mapper配置:
<select
id="BuildBillNo"
parameterType="map"
statementType="CALLABLE">{call procBuild_BillNo(#{prefix},#{serial},#{billNo,mode=OUT,jdbcType=VARCHAR})}
</select>
注意:儲存過程呼叫,其
statementType
必須設定為
CALLABLE;儲存過程/函式的返回結果需要使用 #{引數}=call procName(?,?...)
引數接收,並且需要指定對應的mode為
OUT
型別和
jdbcType
型別 - Java程式碼呼叫:
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("prefix", "H");
map.put("serial", "223232323");
mapper.BuildBillNo(map);
System.out.println("BuildBillNo is " + map.get("billNo"));
- 自動對映
當自動對映查詢結果時,MyBatis會獲取sql返回的列名並在java類中查詢相同名字的屬性(忽略大小寫)。 這意味著如果Mybatis發現了ID列和id屬性,Mybatis會將ID的值賦給id。 通常資料庫列使用大寫單詞命名,單詞間用下劃線分隔;而java屬性一般遵循駝峰命名法。 為了在這兩種命名方式之間啟用自動對映,需要將 mapUnderscoreToCamelCase設定為true。 自動對映甚至在特定的result map下也能工作。在這種情況下,對於每一個result map,所有的ResultSet提供的列, 如果沒有被手工對映,則將被自動對映。自動對映處理完畢後手工對映才會被處理。
通過配置
autoMappingBehavior 來設定自動對映等級,有三種自動對映等級:
- NONE - 禁用自動對映。僅設定手動對映屬性。
- PARTIAL - 將自動對映結果除了那些有內部定義內嵌結果對映的(joins)(預設)
- FULL - 自動對映所有。
預設值是PARTIAL,這是有原因的。當使用FULL時,自動對映會在處理join結果時執行,並且join取得若干相同行的不同實體資料,因此這可能導致非預期的對映。