MyBatis 的真正強大在於它的映射語句,也是它的魔力所在。由於它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼。MyBatis 就是針對 SQL 構建的,並且比普通的方法做的更好。
SQL 映射文件有很少的幾個頂級元素(按照它們應該被定義的順序):
- cache – 給定命名空間的緩存配置。
- cache-ref – 其他命名空間緩存配置的引用。
- resultMap – 是最複雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。
parameterMap– 已廢棄!老式風格的參數映射。內聯參數是首選,這個元素可能在將來被移除,這裏不會記錄。- sql – 可被其他語句引用的可重用語句塊。
- insert – 映射插入語句
- update – 映射更新語句
- delete – 映射刪除語句
- select – 映射查詢語句
下一部分將從語句本身開始來描述每個元素的細節。
查詢(SELECT)
查詢語句是 MyBatis 中最常用的元素之一,光能把數據存到數據庫中價值並不大,如果還能重新取出來纔有用,多數應用也都是查詢比修改要頻繁。對每個插入、更新或刪除操作,通常對應多個查詢操作。這是 MyBatis 的基本原則之一,也是將焦點和努力放到查詢和結果映射的原因。簡單查詢的 select 元素是非常簡單的。比如:
1 <select id="selectPerson" parameterType="int" resultType="hashmap"> 2 SELECT * FROM PERSON WHERE ID = #{id} 3 </select>
這個語句被稱作 selectPerson,接受一個 int(或 Integer)類型的參數,並返回一個 HashMap 類型的對象,其中的鍵是列名,值便是結果行中的對應值。
注意參數符號:
#{id}
這就告訴 MyBatis 創建一個預處理語句參數,通過 JDBC,這樣的一個參數在 SQL 中會由一個“?”來標識,並被傳遞到一個新的預處理語句中,就像這樣:
1 // Similar JDBC code, NOT MyBatis… 2 String selectPerson = "SELECT * FROM PERSON WHERE ID=?"; 3 PreparedStatement ps = conn.prepareStatement(selectPerson); 4 ps.setInt(1,id);
當然,這需要很多單獨的 JDBC 的代碼來提取結果並將它們映射到對象實例中,這就是 MyBatis 節省你時間的地方。我們需要深入瞭解參數和結果映射,細節部分我們下面來了解。
select 元素有很多屬性允許你配置,來決定每條語句的作用細節。
1 <select 2 id="selectPerson" 3 parameterType="int" 4 parameterMap="deprecated" 5 resultType="hashmap" 6 resultMap="personResultMap" 7 flushCache="false" 8 useCache="true" 9 timeout="10000" 10 fetchSize="256" 11 statementType="PREPARED" 12 resultSetType="FORWARD_ONLY">
屬性 | 描述 |
---|---|
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、UPDAET和DELETE)
數據變更語句 insert,update 和 delete 的實現非常接近:
1 <insert 2 id="insertAuthor" 3 parameterType="domain.blog.Author" 4 flushCache="true" 5 statementType="PREPARED" 6 keyProperty="" 7 keyColumn="" 8 useGeneratedKeys="" 9 timeout="20"> 10 11 <update 12 id="updateAuthor" 13 parameterType="domain.blog.Author" 14 flushCache="true" 15 statementType="PREPARED" 16 timeout="20"> 17 18 <delete 19 id="deleteAuthor" 20 parameterType="domain.blog.Author" 21 flushCache="true" 22 statementType="PREPARED" 23 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 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
下面就是 insert,update 和 delete 語句的示例:
1 <insert id="insertAuthor"> 2 insert into Author (id,username,password,email,bio) 3 values (#{id},#{username},#{password},#{email},#{bio}) 4 </insert> 5 6 <update id="updateAuthor"> 7 update Author set 8 username = #{username}, 9 password = #{password}, 10 email = #{email}, 11 bio = #{bio} 12 where id = #{id} 13 </update> 14 15 <delete id="deleteAuthor"> 16 delete from Author where id = #{id} 17 </delete>
如前所述,插入語句的配置規則更加豐富,在插入語句裏面有一些額外的屬性和子元素用來處理主鍵的生成,而且有多種生成方式。
首先,如果你的數據庫支持自動生成主鍵的字段(比如 MySQL 和 SQL Server),那麼你可以設置 useGeneratedKeys=”true”,然後再把 keyProperty 設置到目標屬性上就OK了。例如,如果上面的 Author 表已經對 id 使用了自動生成的列類型,那麼語句可以修改爲:
1 <insert id="insertAuthor" useGeneratedKeys="true" 2 keyProperty="id"> 3 insert into Author (username,password,email,bio) 4 values (#{username},#{password},#{email},#{bio}) 5 </insert>
如果你的數據庫還支持多行插入, 你也可以傳入一個Authors數組或集合,並返回自動生成的主鍵。
1 <insert id="insertAuthor" useGeneratedKeys="true" 2 keyProperty="id"> 3 insert into Author (username, password, email, bio) values 4 <foreach item="item" collection="list" separator=","> 5 (#{item.username}, #{item.password}, #{item.email}, #{item.bio}) 6 </foreach> 7 </insert>
對於不支持自動生成類型的數據庫或可能不支持自動生成主鍵 JDBC 驅動來說,MyBatis 有另外一種方法來生成主鍵。
這裏有一個簡單(甚至很傻)的示例,它可以生成一個隨機 ID(你最好不要這麼做,但這裏展示了 MyBatis 處理問題的靈活性及其所關心的廣度):
1 <insert id="insertAuthor"> 2 <selectKey keyProperty="id" resultType="int" order="BEFORE"> 3 select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1 4 </selectKey> 5 insert into Author 6 (id, username, password, email,bio, favourite_section) 7 values 8 (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR}) 9 </insert>
在上面的示例中,selectKey 元素將會首先運行,Author 的 id 會被設置,然後插入語句會被調用。這給你了一個和數據庫中來處理自動生成的主鍵類似的行爲,避免了使 Java 代碼變得複雜。
selectKey 元素描述如下:
1 <selectKey 2 keyProperty="id" 3 resultType="int" 4 order="BEFORE" 5 statementType="PREPARED">
屬性 | 描述 |
---|---|
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 片段可以被包含在其他語句中,例如:
1 <select id="selectUsers" resultType="map"> 2 select 3 <include refid="userColumns"><property name="alias" value="t1"/></include>, 4 <include refid="userColumns"><property name="alias" value="t2"/></include> 5 from some_table t1 6 cross join some_table t2 7 </select>
屬性值可以用於包含的refid屬性或者包含的字句裏面的屬性值,例如:
1 <sql id="sometable"> 2 ${prefix}Table 3 </sql> 4 5 <sql id="someinclude"> 6 from 7 <include refid="${include_target}"/> 8 </sql> 9 10 <select id="select" resultType="map"> 11 select 12 field1, field2, field3 13 <include refid="someinclude"> 14 <property name="prefix" value="Some"/> 15 <property name="include_target" value="sometable"/> 16 </include> 17 </select>
參數(Parameters)
前面的所有語句中你所見到的都是簡單參數的例子,實際上參數是 MyBatis 非常強大的元素,對於簡單的做法,大概 90% 的情況參數都很少,比如:
1 <select id="selectUsers" resultType="User"> 2 select id, username, password 3 from users 4 where id = #{id} 5 </select>
上面的這個示例說明了一個非常簡單的命名參數映射。參數類型被設置爲 int,這樣這個參數就可以被設置成任何內容。原生的類型或簡單數據類型(比如整型和字符串)因爲沒有相關屬性,它會完全用參數值來替代。然而,如果傳入一個複雜的對象,行爲就會有一點不同了。比如:
1 <insert id="insertUser" parameterType="User"> 2 insert into users (id, username, password) 3 values (#{id}, #{username}, #{password}) 4 </insert>
如果 User 類型的參數對象傳遞到了語句中,id、username 和 password 屬性將會被查找,然後將它們的值傳入預處理語句的參數中。這點對於向語句中傳參是比較好的而且又簡單,不過參數映射的功能遠不止於此。
首先,像 MyBatis 的其他部分一樣,參數也可以指定一個特殊的數據類型。
#{property,javaType=int,jdbcType=NUMERIC}
像 MyBatis 的剩餘部分一樣,javaType 通常可以從參數對象中來去確定,前提是隻要對象不是一個 HashMap。那麼 javaType 應該被確定來保證使用正確類型處理器。
NOTE 如果 null 被當作值來傳遞,對於所有可能爲空的列,JDBC Type 是需要的。你可以自己通過閱讀預處理語句的 setNull() 方法的 JavaDocs 文檔來研究這種情況。
爲了以後定製類型處理方式,你也可以指定一個特殊的類型處理器類(或別名),比如:
#{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 類型,它會自動地被設置爲結果集。
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
MyBatis 也支持很多高級的數據類型,比如結構體,但是當註冊 out 參數時你必須告訴它語句類型名稱。比如(再次提示,在實際中要像這樣不能換行):
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
儘管所有這些強大的選項很多時候你只簡單指定屬性名,其他的事情 MyBatis 會自己去推斷,最多你需要爲可能爲空的列名指定 jdbcType。
1 #{firstName} 2 #{middleInitial,jdbcType=VARCHAR} 3 #{lastName}
字符串替換
默認情況下,使用#{}格式的語法會導致 MyBatis 創建預處理語句屬性並安全地設置值(比如?)。這樣做更安全,更迅速,通常也是首選做法,不過有時你只是想直接在 SQL 語句中插入一個不改變的字符串。比如,像 ORDER BY,你可以這樣來使用:
ORDER BY ${columnName}
這裏 MyBatis 不會修改或轉義字符串。
NOTE 以這種方式接受從用戶輸出的內容並提供給語句中不變的字符串是不安全的,會導致潛在的 SQL 注入攻擊,因此要麼不允許用戶輸入這些字段,要麼自行轉義並檢驗。
Result Maps
resultMap 元素是 MyBatis 中最重要最強大的元素。它就是讓你遠離 90%的需要從結果 集中取出數據的 JDBC 代碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支持的事 情。 事實上, 編寫相似於對複雜語句聯合映射這些等同的代碼, 也許可以跨過上千行的代碼。 ResultMap 的設計就是簡單語句不需要明確的結果映射,而很多複雜語句確實需要描述它們 的關係。
你已經看到簡單映射語句的示例了,但沒有明確的 resultMap。比如:
1 <select id="selectUsers" resultType="map"> 2 select id, username, hashedPassword 3 from some_table 4 where id = #{id} 5 </select>
這樣一個語句簡單作用於所有列被自動映射到 HashMap 的鍵上,這由 resultType 屬性 指定。這在很多情況下是有用的,但是 HashMap 不能很好描述一個領域模型。那樣你的應 用程序將會使用 JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 對象)來作爲領域 模型。MyBatis 對兩者都支持。看看下面這個 JavaBean:
1 package com.someapp.model; 2 public class User { 3 private int id; 4 private String username; 5 private String hashedPassword; 6 7 public int getId() { 8 return id; 9 } 10 public void setId(int id) { 11 this.id = id; 12 } 13 public String getUsername() { 14 return username; 15 } 16 public void setUsername(String username) { 17 this.username = username; 18 } 19 public String getHashedPassword() { 20 return hashedPassword; 21 } 22 public void setHashedPassword(String hashedPassword) { 23 this.hashedPassword = hashedPassword; 24 } 25 }
基於 JavaBean 的規範,上面這個類有 3 個屬性:id,username 和 hashedPassword。這些 在 select 語句中會精確匹配到列名。
這樣的一個 JavaBean 可以被映射到結果集,就像映射到 HashMap 一樣簡單。
1 <select id="selectUsers" resultType="com.someapp.model.User"> 2 select id, username, hashedPassword 3 from some_table 4 where id = #{id} 5 </select>
要記住類型別名是你的夥伴。使用它們你可以不用輸入類的全路徑。比如:
1 <!-- In mybatis-config.xml file --> 2 <typeAlias type="com.someapp.model.User" alias="User"/> 3 4 <!-- In SQL Mapping XML file --> 5 <select id="selectUsers" resultType="User"> 6 select id, username, hashedPassword 7 from some_table 8 where id = #{id} 9 </select>
這些情況下,MyBatis 會在幕後自動創建一個 ResultMap,基於屬性名來映射列到 JavaBean 的屬性上。如果列名沒有精確匹配,你可以在列名上使用 select 字句的別名(一個 基本的 SQL 特性)來匹配標籤。比如:
1 <select id="selectUsers" resultType="User"> 2 select 3 user_id as "id", 4 user_name as "userName", 5 hashed_password as "hashedPassword" 6 from some_table 7 where id = #{id} 8 </select>
ResultMap 最優秀的地方你已經瞭解了很多了,但是你還沒有真正的看到一個。這些簡 單的示例不需要比你看到的更多東西。 只是出於示例的原因, 讓我們來看看最後一個示例中 外部的 resultMap 是什麼樣子的,這也是解決列名不匹配的另外一種方式。
1 <resultMap id="userResultMap" type="User"> 2 <id property="id" column="user_id" /> 3 <result property="username" column="user_name"/> 4 <result property="password" column="hashed_password"/> 5 </resultMap>
引用它的語句使用 resultMap 屬性就行了(注意我們去掉了 resultType 屬性)。比如:
1 <select id="selectUsers" resultMap="userResultMap"> 2 select user_id, user_name, hashed_password 3 from some_table 4 where id = #{id} 5 </select>
高級結果映射
MyBatis 創建的一個想法:數據庫不用永遠是你想要的或需要它們是什麼樣的。而我們 最喜歡的數據庫最好是第三範式或 BCNF 模式,但它們有時不是。如果可能有一個單獨的 數據庫映射,所有應用程序都可以使用它,這是非常好的,但有時也不是。結果映射就是 MyBatis 提供處理這個問題的答案。
比如,我們如何映射下面這個語句?
1 <!-- Very Complex Statement --> 2 <select id="selectBlogDetails" resultMap="detailedBlogResultMap"> 3 select 4 B.id as blog_id, 5 B.title as blog_title, 6 B.author_id as blog_author_id, 7 A.id as author_id, 8 A.username as author_username, 9 A.password as author_password, 10 A.email as author_email, 11 A.bio as author_bio, 12 A.favourite_section as author_favourite_section, 13 P.id as post_id, 14 P.blog_id as post_blog_id, 15 P.author_id as post_author_id, 16 P.created_on as post_created_on, 17 P.section as post_section, 18 P.subject as post_subject, 19 P.draft as draft, 20 P.body as post_body, 21 C.id as comment_id, 22 C.post_id as comment_post_id, 23 C.name as comment_name, 24 C.comment as comment_text, 25 T.id as tag_id, 26 T.name as tag_name 27 from Blog B 28 left outer join Author A on B.author_id = A.id 29 left outer join Post P on B.id = P.blog_id 30 left outer join Comment C on P.id = C.post_id 31 left outer join Post_Tag PT on PT.post_id = P.id 32 left outer join Tag T on PT.tag_id = T.id 33 where B.id = #{id} 34 </select>
你可能想把它映射到一個智能的對象模型,包含一個作者寫的博客,有很多的博文,每 篇博文有零條或多條的評論和標籤。 下面是一個完整的複雜結果映射例子 (假設作者, 博客, 博文, 評論和標籤都是類型的別名) 我們來看看, 。 但是不用緊張, 我們會一步一步來說明。 當天最初它看起來令人生畏,但實際上非常簡單。
1 <!-- Very Complex Result Map --> 2 <resultMap id="detailedBlogResultMap" type="Blog"> 3 <constructor> 4 <idArg column="blog_id" javaType="int"/> 5 </constructor> 6 <result property="title" column="blog_title"/> 7 <association property="author" javaType="Author"> 8 <id property="id" column="author_id"/> 9 <result property="username" column="author_username"/> 10 <result property="password" column="author_password"/> 11 <result property="email" column="author_email"/> 12 <result property="bio" column="author_bio"/> 13 <result property="favouriteSection" column="author_favourite_section"/> 14 </association> 15 <collection property="posts" ofType="Post"> 16 <id property="id" column="post_id"/> 17 <result property="subject" column="post_subject"/> 18 <association property="author" javaType="Author"/> 19 <collection property="comments" ofType="Comment"> 20 <id property="id" column="comment_id"/> 21 </collection> 22 <collection property="tags" ofType="Tag" > 23 <id property="id" column="tag_id"/> 24 </collection> 25 <discriminator javaType="int" column="draft"> 26 <case value="1" resultType="DraftPost"/> 27 </discriminator> 28 </collection> 29 </resultMap>
resultMap 元素有很多子元素和一個值得討論的結構。 下面是 resultMap 元素的概念視圖
resultMap
- constructor - 類在實例化時,用來注入結果到構造方法中
- idArg - ID 參數;標記結果作爲 ID 可以幫助提高整體效能
- arg - 注入到構造方法的一個普通結果
- id – 一個 ID 結果;標記結果作爲 ID 可以幫助提高整體效能
- result – 注入到字段或 JavaBean 屬性的普通結果
- association – 一個複雜的類型關聯;許多結果將包成這種類型
- 嵌入結果映射 – 結果映射自身的關聯,或者參考一個
- collection – 複雜類型的集
- 嵌入結果映射 – 結果映射自身的集,或者參考一個
- discriminator – 使用結果值來決定使用哪個結果映射
- case – 基於某些值的結果映射
- 嵌入結果映射 – 這種情形結果也映射它本身,因此可以包含很多相 同的元素,或者它可以參照一個外部的結果映射。
- case – 基於某些值的結果映射
屬性 | 描述 |
---|---|
id | 當前命名空間中的一個唯一標識,用於標識一個result map. |
type | 類的全限定名, 或者一個類型別名 (內置的別名可以參考上面的表格). |
autoMapping | 如果設置這個屬性,MyBatis將會爲這個ResultMap開啓或者關閉自動映射。這個屬性會覆蓋全局的屬性autoMappingBehavior。默認值爲:unset。 |
通常逐步建立結果映射。單元測試的真正幫助在這裏。如果你嘗試創建 一次創建一個向上面示例那樣的巨大的結果映射, 那麼可能會有錯誤而且很難去控制它 來工作。開始簡單一些,一步一步的發展。而且要進行單元測試!使用該框架的缺點是 它們有時是黑盒(是否可見源代碼) 。你確定你實現想要的行爲的最好選擇是編寫單元 測試。它也可以你幫助得到提交時的錯誤。
id & result
1 <id property="id" column="post_id"/> 2 <result property="subject" column="post_subject"/>
這些是結果映射最基本內容。id 和 result 都映射一個單獨列的值到簡單數據類型(字符 串,整型,雙精度浮點數,日期等)的單獨屬性或字段。這兩者之間的唯一不同是 id 表示的結果將是當比較對象實例時用到的標識屬性。這幫 助來改進整體表現,特別是緩存和嵌入結果映射(也就是聯合映射) 。
每個都有一些屬性:
屬性 | 描述 |
---|---|
property | 映射到列結果的字段或屬性。如果匹配的是存在的,和給定名稱相同 的 JavaBeans 的屬性,那麼就會使用。否則 MyBatis 將會尋找給定名稱 property 的字段。這兩種情形你可以使用通常點式的複雜屬性導航。比如,你 可以這樣映射一些東西: “username” ,或者映射到一些複雜的東西: “address.street.number” 。 |
column | 從數據庫中得到的列名,或者是列名的重命名標籤。這也是通常和會 傳遞給 resultSet.getString(columnName)方法參數中相同的字符串。 |
javaType | 一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名 的列表) 。如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。 然而,如果你映射到的是 HashMap,那麼你應該明確地指定 javaType 來保證所需的行爲。 |
jdbcType | 在這個表格之後的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅 僅需要對插入,更新和刪除操作可能爲空的列進行處理。這是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定 這個類型-但僅僅對可能爲空的值。 |
typeHandler | 我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默 認的類型處理器。這個屬性值是類的完全限定名或者是一個類型處理 器的實現,或者是類型別名。 |
支持的 JDBC 類型
爲了未來的參考,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 |
構造方法
1 <constructor> 2 <idArg column="id" javaType="int"/> 3 <arg column="username" javaType="String"/> 4 </constructor>
對於大多數數據傳輸對象(Data Transfer Object,DTO)類型,屬性可以起作用,而且像 你絕大多數的領域模型, 指令也許是你想使用一成不變的類的地方。 通常包含引用或查詢數 據的表很少或基本不變的話對一成不變的類來說是合適的。 構造方法注入允許你在初始化時 爲類設置屬性的值,而不用暴露出公有方法。MyBatis 也支持私有屬性和私有 JavaBeans 屬 性來達到這個目的,但是一些人更青睞構造方法注入。構造方法元素支持這個。
看看下面這個構造方法:
1 public class User { 2 //... 3 public User(int id, String username) { 4 //... 5 } 6 //... 7 }
爲了向這個構造方法中注入結果,MyBatis 需要通過它的參數的類型來標識構造方法。 Java 沒有自查(反射)參數名的方法。所以當創建一個構造方法元素時,保證參數是按順序 排列的,而且數據類型也是確定的。
1 <constructor> 2 <idArg column="id" javaType="int"/> 3 <arg column="username" javaType="String"/> 4 </constructor>
剩餘的屬性和規則和固定的 id 和 result 元素是相同的。
屬性 | 描述 |
---|---|
column | 來自數據庫的類名,或重命名的列標籤。這和通常傳遞給 resultSet.getString(columnName)方法的字符串是相同的。 |
javaType | 一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名的列表)。 如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。然而,如 果你映射到的是 HashMap,那麼你應該明確地指定 javaType 來保證所需的 行爲。 |
jdbcType | 在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能爲空的列進行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能爲空的值。 |
typeHandler | 我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實現, 或者是類型別名。 |
select | 用於加載複雜類型屬性的映射語句的ID,從column中檢索出來的數據,將作爲此select語句的參數。具體請參考Association標籤。 |
resultMap | ResultMap的ID,可以將嵌套的結果集映射到一個合適的對象樹中,功能和select屬性相似,它可以實現將多表連接操作的結果映射成一個單一的ResultSet。這樣的ResultSet將會將包含重複或部分數據重複的結果集正確的映射到嵌套的對象樹中。爲了實現它, MyBatis允許你 “串聯” ResultMap,以便解決嵌套結果集的問題。想了解更多內容,請參考下面的Association元素。 |
關聯
1 <association property="author" column="blog_author_id" javaType="Author"> 2 <id property="id" column="author_id"/> 3 <result property="username" column="author_username"/> 4 </association>
關聯元素處理“有一個”類型的關係。比如,在我們的示例中,一個博客有一個用戶。 關聯映射就工作於這種結果之上。你指定了目標屬性,來獲取值的列,屬性的 java 類型(很 多情況下 MyBatis 可以自己算出來) ,如果需要的話還有 jdbc 類型,如果你想覆蓋或獲取的 結果值還需要類型控制器。
關聯中不同的是你需要告訴 MyBatis 如何加載關聯。MyBatis 在這方面會有兩種不同的 方式:
- 嵌套查詢:通過執行另外一個 SQL 映射語句來返回預期的複雜類型。
- 嵌套結果:使用嵌套結果映射來處理重複的聯合結果的子集。首先,然讓我們來查看這個元素的屬性。所有的你都會看到,它和普通的只由 select 和
resultMap 屬性的結果映射不同。
屬性 | 描述 |
---|---|
property | 映射到列結果的字段或屬性。如果匹配的是存在的,和給定名稱相同的 property JavaBeans 的屬性, 那麼就會使用。 否則 MyBatis 將會尋找給定名稱的字段。 這兩種情形你可以使用通常點式的複雜屬性導航。比如,你可以這樣映射 一 些 東 西 :“ username ”, 或 者 映 射 到 一 些 復 雜 的 東 西 : “address.street.number” 。 |
javaType | 一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名的列 表) 。如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。然而,如 javaType 果你映射到的是 HashMap,那麼你應該明確地指定 javaType 來保證所需的 行爲。 |
jdbcType | 在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能爲空的列進行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能爲空的值。 |
typeHandler | 我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的 typeHandler 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實現, 或者是類型別名。 |
關聯的嵌套查詢
屬性 | 描述 |
---|---|
column | 來自數據庫的類名,或重命名的列標籤。這和通常傳遞給 resultSet.getString(columnName)方法的字符串是相同的。 column 注 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數對象形式來設置給目標嵌套查詢語句。 |
select | 另外一個映射語句的 ID,可以加載這個屬性映射需要的複雜類型。獲取的 在列屬性中指定的列的值將被傳遞給目標 select 語句作爲參數。表格後面 有一個詳細的示例。 select 注 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數對象形式來設置給目標嵌套查詢語句。 |
fetchType | 可選的。有效值爲 lazy和eager。 如果使用了,它將取代全局配置參數lazyLoadingEnabled。 |
示例:
1 <resultMap id="blogResult" type="Blog"> 2 <association property="author" column="author_id" javaType="Author" select="selectAuthor"/> 3 </resultMap> 4 5 <select id="selectBlog" resultMap="blogResult"> 6 SELECT * FROM BLOG WHERE ID = #{id} 7 </select> 8 9 <select id="selectAuthor" resultType="Author"> 10 SELECT * FROM AUTHOR WHERE ID = #{id} 11 </select>
我們有兩個查詢語句:一個來加載博客,另外一個來加載作者,而且博客的結果映射描 述了“selectAuthor”語句應該被用來加載它的 author 屬性。
其他所有的屬性將會被自動加載,假設它們的列和屬性名相匹配。
這種方式很簡單, 但是對於大型數據集合和列表將不會表現很好。 問題就是我們熟知的 “N+1 查詢問題”。概括地講,N+1 查詢問題可以是這樣引起的:
- 你執行了一個單獨的 SQL 語句來獲取結果列表(就是“+1”)。
- 對返回的每條記錄,你執行了一個查詢語句來爲每個加載細節(就是“N”)。
這個問題會導致成百上千的 SQL 語句被執行。這通常不是期望的。
MyBatis 能延遲加載這樣的查詢就是一個好處,因此你可以分散這些語句同時運行的消 耗。然而,如果你加載一個列表,之後迅速迭代來訪問嵌套的數據,你會調用所有的延遲加 載,這樣的行爲可能是很糟糕的。
所以還有另外一種方法。
關聯的嵌套結果
屬性 | 描述 |
---|---|
resultMap | 這是結果映射的 ID,可以映射關聯的嵌套結果到一個合適的對象圖中。這 是一種替代方法來調用另外一個查詢語句。這允許你聯合多個表來合成到 resultMap 一個單獨的結果集。這樣的結果集可能包含重複,數據的重複組需要被分 解,合理映射到一個嵌套的對象圖。爲了使它變得容易,MyBatis 讓你“鏈 接”結果映射,來處理嵌套結果。一個例子會很容易來仿照,這個表格後 面也有一個示例。 |
columnPrefix | 當連接多表時,你將不得不使用列別名來避免ResultSet中的重複列名。指定columnPrefix允許你映射列名到一個外部的結果集中。 請看後面的例子。 |
notNullColumn | 默認情況下,子對象僅在至少一個列映射到其屬性非空時才創建。 通過對這個屬性指定非空的列將改變默認行爲,這樣做之後Mybatis將僅在這些列非空時才創建一個子對象。 可以指定多個列名,使用逗號分隔。默認值:未設置(unset)。 |
autoMapping | 如果使用了,當映射結果到當前屬性時,Mybatis將啓用或者禁用自動映射。 該屬性覆蓋全局的自動映射行爲。 注意它對外部結果集無影響,所以在select or resultMap屬性中這個是毫無意義的。 默認值:未設置(unset)。 |
在上面你已經看到了一個非常複雜的嵌套關聯的示例。 下面這個是一個非常簡單的示例 來說明它如何工作。代替了執行一個分離的語句,我們聯合博客表和作者表在一起,就像:
1 <select id="selectBlog" resultMap="blogResult"> 2 select 3 B.id as blog_id, 4 B.title as blog_title, 5 B.author_id as blog_author_id, 6 A.id as author_id, 7 A.username as author_username, 8 A.password as author_password, 9 A.email as author_email, 10 A.bio as author_bio 11 from Blog B left outer join Author A on B.author_id = A.id 12 where B.id = #{id} 13 </select>
注意這個聯合查詢, 以及採取保護來確保所有結果被唯一而且清晰的名字來重命名。 這使得映射非常簡單。現在我們可以映射這個結果:
1 <resultMap id="blogResult" type="Blog"> 2 <id property="id" column="blog_id" /> 3 <result property="title" column="blog_title"/> 4 <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/> 5 </resultMap> 6 7 <resultMap id="authorResult" type="Author"> 8 <id property="id" column="author_id"/> 9 <result property="username" column="author_username"/> 10 <result property="password" column="author_password"/> 11 <result property="email" column="author_email"/> 12 <result property="bio" column="author_bio"/> 13 </resultMap>
在上面的示例中你可以看到博客的作者關聯代表着“authorResult”結果映射來加載作 者實例。
非常重要: 在嵌套據誒過映射中 id 元素扮演了非常重要的角色。應應該通常指定一個 或多個屬性,它們可以用來唯一標識結果。實際上就是如果你離開她了,但是有一個嚴重的 性能問題時 MyBatis 仍然可以工作。選擇的屬性越少越好,它們可以唯一地標識結果。主鍵 就是一個顯而易見的選擇(儘管是聯合主鍵)。
現在,上面的示例用了外部的結果映射元素來映射關聯。這使得 Author 結果映射可以 重用。然而,如果你不需要重用它的話,或者你僅僅引用你所有的結果映射合到一個單獨描 述的結果映射中。你可以嵌套結果映射。這裏給出使用這種方式的相同示例:
1 <resultMap id="blogResult" type="Blog"> 2 <id property="id" column="blog_id" /> 3 <result property="title" column="blog_title"/> 4 <association property="author" javaType="Author"> 5 <id property="id" column="author_id"/> 6 <result property="username" column="author_username"/> 7 <result property="password" column="author_password"/> 8 <result property="email" column="author_email"/> 9 <result property="bio" column="author_bio"/> 10 </association> 11 </resultMap>
如果blog有一個co-author怎麼辦? select語句將看起來這個樣子:
1 <select id="selectBlog" resultMap="blogResult"> 2 select 3 B.id as blog_id, 4 B.title as blog_title, 5 A.id as author_id, 6 A.username as author_username, 7 A.password as author_password, 8 A.email as author_email, 9 A.bio as author_bio, 10 CA.id as co_author_id, 11 CA.username as co_author_username, 12 CA.password as co_author_password, 13 CA.email as co_author_email, 14 CA.bio as co_author_bio 15 from Blog B 16 left outer join Author A on B.author_id = A.id 17 left outer join Author CA on B.co_author_id = CA.id 18 where B.id = #{id} 19 </select>
再次調用Author的resultMap將定義如下:
1 <resultMap id="authorResult" type="Author"> 2 <id property="id" column="author_id"/> 3 <result property="username" column="author_username"/> 4 <result property="password" column="author_password"/> 5 <result property="email" column="author_email"/> 6 <result property="bio" column="author_bio"/> 7 </resultMap>
因爲結果中的列名與resultMap中的列名不同。 你需要指定columnPrefix去重用映射co-author結果的resultMap。
1 <resultMap id="blogResult" type="Blog"> 2 <id property="id" column="blog_id" /> 3 <result property="title" column="blog_title"/> 4 <association property="author" 5 resultMap="authorResult" /> 6 <association property="coAuthor" 7 resultMap="authorResult" 8 columnPrefix="co_" /> 9 </resultMap>
上面你已經看到了如何處理“有一個”類型關聯。但是“有很多個”是怎樣的?下面這 個部分就是來討論這個主題的。
集合
1 <collection property="posts" ofType="domain.blog.Post"> 2 <id property="id" column="post_id"/> 3 <result property="subject" column="post_subject"/> 4 <result property="body" column="post_body"/> 5 </collection>
集合元素的作用幾乎和關聯是相同的。實際上,它們也很相似,文檔的異同是多餘的。 所以我們更多關注於它們的不同。我們來繼續上面的示例,一個博客只有一個作者。但是博客有很多文章。在博客類中, 這可以由下面這樣的寫法來表示:
private List<Post> posts;
要映射嵌套結果集合到 List 中,我們使用集合元素。就像關聯元素一樣,我們可以從 連接中使用嵌套查詢,或者嵌套結果。
集合的嵌套查詢
首先,讓我們看看使用嵌套查詢來爲博客加載文章。
1 <resultMap id="blogResult" type="Blog"> 2 <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/> 3 </resultMap> 4 5 <select id="selectBlog" resultMap="blogResult"> 6 SELECT * FROM BLOG WHERE ID = #{id} 7 </select> 8 9 <select id="selectPostsForBlog" resultType="Blog"> 10 SELECT * FROM POST WHERE BLOG_ID = #{id} 11 </select>
這裏你應該注意很多東西,但大部分代碼和上面的關聯元素是非常相似的。首先,你應 該注意我們使用的是集合元素。然後要注意那個新的“ofType”屬性。這個屬性用來區分 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"/>
集合的嵌套結果
至此,你可以猜測集合的嵌套結果是如何來工作的,因爲它和關聯完全相同,除了它應 用了一個“ofType”屬性
First, let's look at the SQL:
1 <select id="selectBlog" resultMap="blogResult"> 2 select 3 B.id as blog_id, 4 B.title as blog_title, 5 B.author_id as blog_author_id, 6 P.id as post_id, 7 P.subject as post_subject, 8 P.body as post_body, 9 from Blog B 10 left outer join Post P on B.id = P.blog_id 11 where B.id = #{id} 12 </select>
我們又一次聯合了博客表和文章表,而且關注於保證特性,結果列標籤的簡單映射。現 在用文章映射集合映射博客,可以簡單寫爲:
1 <resultMap id="blogResult" type="Blog"> 2 <id property="id" column="blog_id" /> 3 <result property="title" column="blog_title"/> 4 <collection property="posts" ofType="Post"> 5 <id property="id" column="post_id"/> 6 <result property="subject" column="post_subject"/> 7 <result property="body" column="post_body"/> 8 </collection> 9 </resultMap>
同樣,要記得 id 元素的重要性,如果你不記得了,請閱讀上面的關聯部分。
同樣, 如果你引用更長的形式允許你的結果映射的更多重用, 你可以使用下面這個替代 的映射:
1 <resultMap id="blogResult" type="Blog"> 2 <id property="id" column="blog_id" /> 3 <result property="title" column="blog_title"/> 4 <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/> 5 </resultMap> 6 7 <resultMap id="blogPostResult" type="Post"> 8 <id property="id" column="id"/> 9 <result property="subject" column="subject"/> 10 <result property="body" column="body"/> 11 </resultMap>
這個對你所映射的內容沒有深度,廣度或關聯和集合相聯合的限制。當映射它們 時你應該在大腦中保留它們的表現。 你的應用在找到最佳方法前要一直進行的單元測試和性 能測試。好在 myBatis 讓你後來可以改變想法,而不對你的代碼造成很小(或任何)影響。
高級關聯和集合映射是一個深度的主題。文檔只能給你介紹到這了。加上一點聯繫,你 會很快清楚它們的用法。
鑑別器
1 <discriminator javaType="int" column="draft"> 2 <case value="1" resultType="DraftPost"/> 3 </discriminator>
有時一個單獨的數據庫查詢也許返回很多不同 (但是希望有些關聯) 數據類型的結果集。 鑑別器元素就是被設計來處理這個情況的, 還有包括類的繼承層次結構。 鑑別器非常容易理 解,因爲它的表現很像 Java 語言中的 switch 語句。
定義鑑別器指定了 column 和 javaType 屬性。 列是 MyBatis 查找比較值的地方。 JavaType 是需要被用來保證等價測試的合適類型(儘管字符串在很多情形下都會有用)。比如:
1 <resultMap id="vehicleResult" type="Vehicle"> 2 <id property="id" column="id" /> 3 <result property="vin" column="vin"/> 4 <result property="year" column="year"/> 5 <result property="make" column="make"/> 6 <result property="model" column="model"/> 7 <result property="color" column="color"/> 8 <discriminator javaType="int" column="vehicle_type"> 9 <case value="1" resultMap="carResult"/> 10 <case value="2" resultMap="truckResult"/> 11 <case value="3" resultMap="vanResult"/> 12 <case value="4" resultMap="suvResult"/> 13 </discriminator> 14 </resultMap>
在這個示例中, MyBatis 會從結果集中得到每條記錄, 然後比較它的 vehicle 類型的值。 如果它匹配任何一個鑑別器的實例,那麼就使用這個實例指定的結果映射。換句話說,這樣 做完全是剩餘的結果映射被忽略(除非它被擴展,這在第二個示例中討論) 。如果沒有任何 一個實例相匹配,那麼 MyBatis 僅僅使用鑑別器塊外定義的結果映射。所以,如果 carResult 按如下聲明:
1 <resultMap id="carResult" type="Car"> 2 <result property="doorCount" column="door_count" /> 3 </resultMap>
那麼只有 doorCount 屬性會被加載。這步完成後完整地允許鑑別器實例的獨立組,儘管 和父結果映射可能沒有什麼關係。這種情況下,我們當然知道 cars 和 vehicles 之間有關係, 如 Car 是一個 Vehicle 實例。因此,我們想要剩餘的屬性也被加載。我們設置的結果映射的 簡單改變如下。
1 <resultMap id="carResult" type="Car" extends="vehicleResult"> 2 <result property="doorCount" column="door_count" /> 3 </resultMap>
現在 vehicleResult 和 carResult 的屬性都會被加載了。
儘管曾經有些人會發現這個外部映射定義會多少有一些令人厭煩之處。 因此還有另外一 種語法來做簡潔的映射風格。比如:
1 <resultMap id="vehicleResult" type="Vehicle"> 2 <id property="id" column="id" /> 3 <result property="vin" column="vin"/> 4 <result property="year" column="year"/> 5 <result property="make" column="make"/> 6 <result property="model" column="model"/> 7 <result property="color" column="color"/> 8 <discriminator javaType="int" column="vehicle_type"> 9 <case value="1" resultType="carResult"> 10 <result property="doorCount" column="door_count" /> 11 </case> 12 <case value="2" resultType="truckResult"> 13 <result property="boxSize" column="box_size" /> 14 <result property="extendedCab" column="extended_cab" /> 15 </case> 16 <case value="3" resultType="vanResult"> 17 <result property="powerSlidingDoor" column="power_sliding_door" /> 18 </case> 19 <case value="4" resultType="suvResult"> 20 <result property="allWheelDrive" column="all_wheel_drive" /> 21 </case> 22 </discriminator> 23 </resultMap>
這些都是結果映射, 如果你不指定任何結果, 那麼 MyBatis 將會爲你自動匹配列 和屬性。所以這些例子中的大部分是很冗長的,而其實是不需要的。也就是說,很多數據庫 是很複雜的,我們不太可能對所有示例都能依靠它。
自動映射
正如你在前面一節看到的,在簡單的場景下,MyBatis可以替你自動映射查詢結果。 如果遇到複雜的場景,你需要構建一個result map。 但是在本節你將看到,你也可以混合使用這兩種策略。 讓我們到深一點的層面上看看自動映射是怎樣工作的。
當自動映射查詢結果時,MyBatis會獲取sql返回的列名並在java類中查找相同名字的屬性(忽略大小寫)。 這意味着如果Mybatis發現了ID列和id屬性,Mybatis會將ID的值賦給id。
通常數據庫列使用大寫單詞命名,單詞間用下劃線分隔;而java屬性一般遵循駝峯命名法。 爲了在這兩種命名方式之間啓用自動映射,需要將 mapUnderscoreToCamelCase設置爲true。
自動映射甚至在特定的result map下也能工作。在這種情況下,對於每一個result map,所有的ResultSet提供的列, 如果沒有被手工映射,則將被自動映射。自動映射處理完畢後手工映射纔會被處理。 在接下來的例子中, id 和 userName列將被自動映射, hashed_password 列將根據配置映射。
1 <select id="selectUsers" resultMap="userResultMap"> 2 select 3 user_id as "id", 4 user_name as "userName", 5 hashed_password 6 from some_table 7 where id = #{id} 8 </select>
1 <resultMap id="userResultMap" type="User"> 2 <result property="password" column="hashed_password"/> 3 </resultMap>
有三種自動映射等級:
- NONE - 禁用自動映射。僅設置手動映射屬性。
- PARTIAL - 將自動映射結果除了那些有內部定義內嵌結果映射的(joins).
- FULL - 自動映射所有。
默認值是PARTIAL,這是有原因的。當使用FULL時,自動映射會在處理join結果時執行,並且join取得若干相同行的不同實體數據,因此這可能導致非預期的映射。下面的例子將展示這種風險:
1 <select id="selectBlog" resultMap="blogResult"> 2 select 3 B.id, 4 B.title, 5 A.username, 6 from Blog B left outer join Author A on B.author_id = A.id 7 where B.id = #{id} 8 </select>
1 <resultMap id="blogResult" type="Blog"> 2 <association property="author" resultMap="authorResult"/> 3 </resultMap> 4 5 <resultMap id="authorResult" type="Author"> 6 <result property="username" column="author_username"/> 7 </resultMap>
在結果中Blog和Author均將自動映射。但是注意Author有一個id屬性,在ResultSet中有一個列名爲id, 所以Author的id將被填充爲Blog的id,這不是你所期待的。所以需要謹慎使用FULL。
通過添加autoMapping屬性可以忽略自動映射等級配置,你可以啓用或者禁用自動映射指定的ResultMap。
1 <resultMap id="userResultMap" type="User" autoMapping="false"> 2 <result property="password" column="hashed_password"/> 3 </resultMap>
緩存
MyBatis 包含一個非常強大的查詢緩存特性,它可以非常方便地配置和定製。MyBatis 3 中的緩存實現的很多改進都已經實現了,使得它更加強大而且易於配置。
默認情況下是沒有開啓緩存的,除了局部的 session 緩存,可以增強變現而且處理循環 依賴也是必須的。要開啓二級緩存,你需要在你的 SQL 映射文件中添加一行:
<cache/>
字面上看就是這樣。這個簡單語句的效果如下:
- 映射語句文件中的所有 select 語句將會被緩存。
- 映射語句文件中的所有 insert,update 和 delete 語句會刷新緩存。
- 緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回。
- 根據時間表(比如 no Flush Interval,沒有刷新間隔), 緩存不會以任何時間順序 來刷新。
- 緩存會存儲列表集合或對象(無論查詢方法返回什麼)的 1024 個引用。
- 緩存會被視爲是 read/write(可讀/可寫)的緩存,意味着對象檢索不是共享的,而 且可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改。
所有的這些屬性都可以通過緩存元素的屬性來修改。比如:
1 <cache 2 eviction="FIFO" 3 flushInterval="60000" 4 size="512" 5 readOnly="true"/>
這個更高級的配置創建了一個 FIFO 緩存,並每隔 60 秒刷新,存數結果對象或列表的 512 個引用,而且返回的對象被認爲是隻讀的,因此在不同線程中的調用者之間修改它們會 導致衝突。
可用的收回策略有:
- LRU – 最近最少使用的:移除最長時間不被使用的對象。
- FIFO – 先進先出:按對象進入緩存的順序來移除它們。
- SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
- WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。
默認的是 LRU。
flushInterval(刷新間隔)可以被設置爲任意的正整數,而且它們代表一個合理的毫秒 形式的時間段。默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新。
size(引用數目)可以被設置爲任意正整數,要記住你緩存的對象數目和你運行環境的 可用內存資源數目。默認值是 1024。
readOnly(只讀)屬性可以被設置爲 true 或 false。只讀的緩存會給所有調用者返回緩 存對象的相同實例。因此這些對象不能被修改。這提供了很重要的性能優勢。可讀寫的緩存 會返回緩存對象的拷貝(通過序列化) 。這會慢一些,但是安全,因此默認是 false。
使用自定義緩存
除了這些自定義緩存的方式, 你也可以通過實現你自己的緩存或爲其他第三方緩存方案 創建適配器來完全覆蓋緩存行爲。
<cache type="com.domain.something.MyCustomCache"/>
這個示 例展 示了 如何 使用 一個 自定義 的緩 存實 現。type 屬 性指 定的 類必 須實現 org.mybatis.cache.Cache 接口。這個接口是 MyBatis 框架中很多複雜的接口之一,但是簡單 給定它做什麼就行。
1 public interface Cache { 2 String getId(); 3 int getSize(); 4 void putObject(Object key, Object value); 5 Object getObject(Object key); 6 boolean hasKey(Object key); 7 Object removeObject(Object key); 8 void clear(); 9 }
要配置你的緩存, 簡單和公有的 JavaBeans 屬性來配置你的緩存實現, 而且是通過 cache 元素來傳遞屬性, 比如, 下面代碼會在你的緩存實現中調用一個稱爲 “setCacheFile(String file)” 的方法:
你可以使用所有簡單類型作爲 JavaBeans 的屬性,MyBatis 會進行轉換。
1 public interface InitializingObject { 2 void initialize() throws Exception; 3 }
記得緩存配置和緩存實例是綁定在 SQL 映射文件的命名空間是很重要的。因此,所有 在相同命名空間的語句正如綁定的緩存一樣。 語句可以修改和緩存交互的方式, 或在語句的 語句的基礎上使用兩種簡單的屬性來完全排除它們。默認情況下,語句可以這樣來配置:
1 <select ... flushCache="false" useCache="true"/> 2 <insert ... flushCache="true"/> 3 <update ... flushCache="true"/> 4 <delete ... flushCache="true"/>
因爲那些是默認的,你明顯不能明確地以這種方式來配置一條語句。相反,如果你想改 變默認的行爲,只能設置 flushCache 和 useCache 屬性。比如,在一些情況下你也許想排除 從緩存中查詢特定語句結果,或者你也許想要一個查詢語句來刷新緩存。相似地,你也許有 一些更新語句依靠執行而不需要刷新緩存。
參照緩存
回想一下上一節內容, 這個特殊命名空間的唯一緩存會被使用或者刷新相同命名空間內 的語句。也許將來的某個時候,你會想在命名空間中共享相同的緩存配置和實例。在這樣的 情況下你可以使用 cache-ref 元素來引用另外一個緩存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>