MyBatis中XML 映射文件中常見的標簽說明

SQL 映射文件隻有很少的幾個頂級元素(按照應被定義的順序列出):

  • cache – 對給定命名空間的緩存配置。
  • cache-ref – 對其他命名空間緩存配置的引用。
  • resultMap – 是最復雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。
  • parameterMap – 已被廢棄!老式風格的參數映射。更好的辦法是使用內聯參數,此元素可能在將來被移除。
  • sql – 可被其他語句引用的可重用語句塊。
  • insert – 映射插入語句
  • update – 映射更新語句
  • delete – 映射刪除語句
  • select – 映射查詢語句

select

<select id="selectPerson" parameterType="int" resultType="hashmap">
   SELECT * FROM PERSON WHERE ID = #{id}
 </select>

這個語句被稱作 selectPerson,接受一個 int(或 Integer)類型的參數,並返回一個 HashMap 類型的對象,其中的鍵是列名,值便是結果行中的對應值。

註意參數符號:#{id}

這就告訴 MyBatis 創建一個預處理語句(PreparedStatement)參數,在 JDBC 中,這樣的一個參數在 SQL 中會由一個“?”來標識,並被傳遞到一個新的預處理語句中,就像這樣:

// 近似的 JDBC 代碼,非 MyBatis 代碼...
 String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
 PreparedStatement ps = conn.prepareStatement(selectPerson);
 ps.setInt(1,id);
 <select
   id="selectPerson"
   parameterType="int"
   parameterMap="deprecated"
   resultType="hashmap"
   resultMap="personResultMap"
   flushCache="false"
   useCache="true"
   timeout="10"
   fetchSize="256"
   statementType="PREPARED"
   resultSetType="FORWARD_ONLY">
屬性 描述
id 在命名空間中唯一的標識符,可以被用來引用這條語句。
parameterType 將會傳入這條語句的參數類的完全限定名或別名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器(TypeHandler) 推斷出具體傳入語句的參數,默認值為未設置(unset)。
parameterMap 這是引用外部 parameterMap 的已經被廢棄的方法。請使用內聯參數映射和 parameterType 屬性。
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 或 DEFAULT(等價於 unset) 中的一個,默認值為 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 可以通過類型處理器推斷出具體傳入語句的參數,默認值為未設置(unset)。
parameterMap 這是引用外部 parameterMap 的已經被廢棄的方法。請使用內聯參數映射和 parameterType 屬性。
flushCache 將其設置為 true 後,隻要語句被調用,都會導致本地緩存和二級緩存被清空,默認值:true(對於 insert、update 和 delete 語句)。
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 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。

生成主鍵

 <selectKey
   keyProperty="id"
   resultType="int"
   order="BEFORE"
   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 類型。
 <selectKey resultType="java.lang.Long" order="AFTER" keyProperty="id">
   SELECT LAST_INSERT_ID() AS id
 </selectKey>

sql

這個元素可以被用來定義可重用的 SQL 代碼段,這些 SQL 代碼可以被包含在其他語句中。它可以(在加載的時候)被靜態地設置參數。 在不同的包含語句中可以設置不同的值到參數占位符上。比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </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>

結果映射

resultMap 元素是 MyBatis 中最重要最強大的元素,在一些情形下允許你進行一些 JDBC 不支持的操作。實際上,在為一些比如連接的復雜語句編寫映射代碼的時候,一份 resultMap 能夠代替實現同等功能的長達數千行的代碼。ResultMap 的設計思想是,對於簡單的語句根本不需要配置顯式的結果映射,而對於復雜一點的語句隻需要描述它們的關系就行瞭。

在JavaBean中定義一個有 3 個屬性:id,username 和 hashedPassword的類,然後在mapper.xml中,這些屬性會對應到 select 語句中的列名,這樣的一個 JavaBean 可以被映射到 ResultSet,就像映射到 HashMap 一樣簡單。

<select id="selectUsers" resultType="com.someapp.model.User">
   select id, username, hashedPassword
   from some_table
   where id = #{id}
 </select>

像第三中的第三個例子,也可以定義外部resultMap,這也是解決列名不匹配的另外一種方式。

 <resultMap id="userResultMap" type="User">
   <id property="id" column="user_id" />
   <result property="username" column="user_name"/>
   <result property="password" column="hashed_password"/>
 </resultMap>
 ​
 <select id="selectUsers" resultMap="userResultMap">
   select user_id, user_name, hashed_password
   from some_table
   where id = #{id}
 </select>

結果映射(resultMap)

  • constructor-用於在實例化類時,註入結果到構造方法中
  • idArg – ID 參數;標記出作為 ID 的結果可以幫助提高整體性能
  • arg – 將被註入到構造方法的一個普通結果
  • id – 一個 ID 結果;標記出作為 ID 的結果可以幫助提高整體性能
  • result – 註入到字段或 JavaBean 屬性的普通結果
  • association– 一個復雜類型的關聯;許多結果將包裝成這種類型
  • 嵌套結果映射 – 關聯本身可以是一個 resultMap 元素,或者從別處引用一個
  • collection– 一個復雜類型的集合
  • 嵌套結果映射 – 集合本身可以是一個 resultMap 元素,或者從別處引用一個
  • discriminator– 使用結果值來決定使用哪個resultMap
  • case– 基於某些值的結果映射
  • 嵌套結果映射 – case 本身可以是一個 resultMap 元素,因此可以具有相同的結構和元素,或者從別處引用一個。
屬性 描述
id 當前命名空間中的一個唯一標識,用於標識一個結果映射。
type 類的完全限定名, 或者一個類型別名(關於內置的類型別名,可以參考上面的表格)。
autoMapping 如果設置這個屬性,MyBatis將會為本結果映射開啟或者關閉自動映射。 這個屬性會覆蓋全局的屬性 autoMappingBehavior。默認值:未設置(unset)。

id & result

 <id property="id" column="post_id"/>
 <result property="subject" column="post_subject"/>

這些是結果映射最基本的內容。id 和 result 元素都將一個列的值映射到一個簡單數據類型(String, int, double, Date 等)的屬性或字段。

這兩者之間的唯一不同是,id 元素表示的結果將是對象的標識屬性,這會在比較對象實例時用到。 這樣可以提高整體的性能,尤其是進行緩存和嵌套結果映射(也就是連接映射)的時候。

屬性 描述
property 映射到列結果的字段或屬性。如果用來匹配的 JavaBean 存在給定名字的屬性,那麼它將會被使用。否則 MyBatis 將會尋找給定名稱的字段。 無論是哪一種情形,你都可以使用通常的點式分隔形式進行復雜屬性導航。 比如,你可以這樣映射一些簡單的東西:“username”,或者映射到一些復雜的東西上:“address.street.number”。
column 數據庫中的列名,或者是列的別名。一般情況下,這和傳遞給 resultSet.getString(columnName) 方法的參數一樣。
javaType 一個 Java 類的完全限定名,或一個類型別名(關於內置的類型別名,可以參考上面的表格)。 如果你映射到一個 JavaBean,MyBatis 通常可以推斷類型。然而,如果你映射到的是 HashMap,那麼你應該明確地指定 javaType 來保證行為與期望的相一致。
jdbcType JDBC 類型,所支持的 JDBC 類型參見這個表格之後的“支持的 JDBC 類型”。 隻需要在可能執行插入、更新和刪除的且允許空值的列上指定 JDBC 類型。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 編程,你需要對可能存在空值的列指定這個類型。
typeHandler 我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的類型處理器。 這個屬性值是一個類型處理器實現類的完全限定名,或者是類型別名。

緩存

默認情況下,隻啟用瞭本地的會話緩存,它僅僅對一個會話中的數據進行緩存。 要啟用全局的二級緩存,隻需要在你的 SQL 映射文件中添加一行:<cache/>

基本上就是這樣。這個簡單語句的效果如下:

  • 映射語句文件中的所有 select 語句的結果將會被緩存。
  • 映射語句文件中的所有 insert、update 和 delete 語句會刷新緩存。
  • 緩存會使用最近最少使用算法(LRU, Least Recently Used)算法來清除不需要的緩存。
  • 緩存不會定時進行刷新(也就是說,沒有刷新間隔)。
  • 緩存會保存列表或對象(無論查詢方法返回哪種)的 1024 個引用。
  • 緩存會被視為讀/寫緩存,這意味著獲取到的對象並不是共享的,可以安全地被調用者修改,而不幹擾其他調用者或線程所做的潛在修改。

提示 緩存隻作用於 cache 標簽所在的映射文件中的語句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的語句將不會被默認緩存。你需要使用 @CacheNamespaceRef 註解指定緩存作用域。

這些屬性可以通過 cache 元素的屬性來修改。比如:

 <cache
   eviction="FIFO"
   flushInterval="60000"
   size="512"
   readOnly="true"/>

這個更高級的配置創建瞭一個 FIFO 緩存,每隔 60 秒刷新,最多可以存儲結果對象或列表的 512 個引用,而且返回的對象被認為是隻讀的,因此對它們進行修改可能會在不同線程中的調用者產生沖突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最長時間不被使用的對象。
  • FIFO – 先進先出:按對象進入緩存的順序來移除它們。
  • SOFT – 軟引用:基於垃圾回收器狀態和軟引用規則移除對象。
  • WEAK – 弱引用:更積極地基於垃圾收集器狀態和弱引用規則移除對象。

默認的清除策略是 LRU。

flushInterval(刷新間隔)屬性可以被設置為任意的正整數,設置的值應該是一個以毫秒為單位的合理時間量。 默認情況是不設置,也就是沒有刷新間隔,緩存僅僅會在調用語句時刷新。

size(引用數目)屬性可以被設置為任意正整數,要註意欲緩存對象的大小和運行環境中可用的內存資源。默認值是 1024。

readOnly(隻讀)屬性可以被設置為 true 或 false。隻讀的緩存會給所有調用者返回緩存對象的相同實例。 因此這些對象不能被修改。這就提供瞭可觀的性能提升。而可讀寫的緩存會(通過序列化)返回緩存對象的拷貝。 速度上會慢一些,但是更安全,因此默認值是 false。

提示 二級緩存是事務性的。這意味著,當 SqlSession 完成並提交時,或是完成並回滾,但沒有執行 flushCache=true 的 insert/delete/update 語句時,緩存會獲得更新。

使用自定義緩存

除瞭上述自定義緩存的方式,你也可以通過實現你自己的緩存,或為其他第三方緩存方案創建適配器,來完全覆蓋緩存行為。

 <cache type="com.domain.something.MyCustomCache"/>

例子:

type 屬性指定的類必須實現 org.mybatis.cache.Cache 接口,且提供一個接受 String 參數作為 id 的構造器。 這個接口是 MyBatis 框架中許多復雜的接口之一,但是行為卻非常簡單。

 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();
 }

動態SQL

  • Mybatis 動態 SQL ,可以讓我們在 XML 映射文件內,以 XML 標簽的形式編寫動態 SQL ,完成邏輯判斷和動態拼接 SQL 的功能。
  • Mybatis 提供瞭 9 種動態 SQL 標簽:<if />、<choose />、<when />、<otherwise />、<trim />、<where />、<set />、<foreach />、<bind /> 。
  • 其執行原理為,使用 OGNL 的表達式,從 SQL 參數對象中計算表達式的值,根據表達式的值動態拼接 SQL ,以此來完成動態 SQL 的功能。

if

動態 SQL 通常要做的事情是根據條件包含 where 子句的一部分。比如:

 <select id="findActiveBlogWithTitleLike"
      resultType="Blog">
   SELECT * FROM BLOG
   WHERE state = ‘ACTIVE'
   <if test="title != null">
     AND title like #{title}
   </if>
 </select>

這條語句提供瞭一種可選的查找文本功能。如果沒有傳入“title”,那麼所有處於“ACTIVE”狀態的BLOG都會返回;反之若傳入瞭“title”,那麼就會對“title”一列進行模糊查找並返回 BLOG 結果(“title”參數值是可以包含一些掩碼或通配符的)。

通過“title”和“author”兩個參數進行可選搜索:

 <select id="findActiveBlogLike"
      resultType="Blog">
   SELECT * FROM BLOG WHERE state = ‘ACTIVE'
   <if test="title != null">
     AND title like #{title}
   </if>
   <if test="author != null and author.name != null">
     AND author_name like #{author.name}
   </if>
 </select>

choose, when, otherwise

有時我們不想應用到所有的條件語句,而隻想從中擇其一項。針對這種情況,MyBatis 提供瞭 choose 元素,它有點像 Java 中的 switch 語句。

這次變為提供瞭“title”就按“title”查找,提供瞭“author”就按“author”查找的情形,若兩者都沒有提供,就返回所有符合條件的 BLOG(實際情況可能是由管理員按一定策略選出 BLOG 列表,而不是返回大量無意義的隨機結果)。

 <select id="findActiveBlogLike"
      resultType="Blog">
   SELECT * FROM BLOG WHERE state = ‘ACTIVE'
   <choose>
     <when test="title != null">
       AND title like #{title}
     </when>
     <when test="author != null and author.name != null">
       AND author_name like #{author.name}
     </when>
     <otherwise>
       AND featured = 1
     </otherwise>
   </choose>
 </select>

trim, where, set

上面的幾個例子,如果條件不滿足的話,會拼湊成不成一條sql語句,導致無法查詢,如:SELECT * FROM BLOG WHERE

 <select id="findActiveBlogLike"
      resultType="Blog">
   SELECT * FROM BLOG
   <where>
     <if test="state != null">
          state = #{state}
     </if>
     <if test="title != null">
         AND title like #{title}
     </if>
     <if test="author != null and author.name != null">
         AND author_name like #{author.name}
     </if>
   </where>
 </select>

where 元素隻會在至少有一個子元素的條件返回 SQL 子句的情況下才去插入“WHERE”子句。而且,若語句的開頭為“AND”或“OR”,where 元素也會將它們去除。

如果 where 元素沒有按正常套路出牌,我們可以通過自定義 trim 元素來定制 where 元素的功能。比如,和 where 元素等價的自定義 trim 元素為:

 <trim prefix="WHERE" prefixOverrides="AND |OR ">
   ...
 </trim>

prefixOverrides 屬性會忽略通過管道分隔的文本序列(註意此例中的空格也是必要的)。它的作用是移除所有指定在 prefixOverrides 屬性中的內容,並且插入 prefix 屬性中指定的內容。

類似的用於動態更新語句的解決方案叫做 set。set 元素可以用於動態包含需要更新的列,而舍去其它的。比如:

 <update id="updateAuthorIfNecessary">
   update Author
     <set>
       <if test="username != null">username=#{username},</if>
       <if test="password != null">password=#{password},</if>
       <if test="email != null">email=#{email},</if>
       <if test="bio != null">bio=#{bio}</if>
     </set>
   where id=#{id}
 </update>

這裡,set 元素會動態前置 SET 關鍵字,同時也會刪掉無關的逗號,因為用瞭條件語句之後很可能就會在生成的 SQL 語句的後面留下這些逗號。(譯者註:因為用的是“if”元素,若最後一個“if”沒有匹配上而前面的匹配上,SQL 語句的最後就會有一個逗號遺留)

若你對 set 元素等價的自定義 trim 元素的代碼感興趣,那這就是它的真面目:

 <trim prefix="SET" suffixOverrides=",">
   ...
 </trim>

註意這裡我們刪去的是後綴值,同時添加瞭前綴值。

foreach

動態 SQL 的另外一個常用的操作需求是對一個集合進行遍歷,通常是在構建 IN 條件語句的時候。比如:

 <select id="selectPostIn" resultType="domain.blog.Post">
   SELECT *
   FROM POST P
   WHERE ID in
   <foreach item="item" index="index" collection="list"
       open="(" separator="," close=")">
         #{item}
   </foreach>
 </select>

foreach 元素的功能非常強大,它允許你指定一個集合,聲明可以在元素體內使用的集合項(item)和索引(index)變量。它也允許你指定開頭與結尾的字符串以及在迭代結果之間放置分隔符。這個元素是很智能的,因此它不會偶然地附加多餘的分隔符。

註意

可以將任何可迭代對象(如 List、Set 等)、Map 對象或者數組對象傳遞給 foreach 作為集合參數。當使用可迭代對象或者數組時,index 是當前迭代的次數,item 的值是本次迭代獲取的元素。當使用 Map 對象(或者 Map.Entry 對象的集合)時,index 是鍵,item 是值。

bind

bind 元素可以從 OGNL 表達式中創建一個變量並將其綁定到上下文。比如:

 <select id="selectBlogsLike" resultType="Blog">
   <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
   SELECT * FROM BLOG
   WHERE title LIKE #{pattern}
 </select>

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: