MyBatis系列之對映檔案之sql、Parameters、cache和cache-ref

weixin_33670713發表於2016-10-26

內容多來源於MyBatis官方文件

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 的剩餘部分一樣,javaType 通常可以從引數物件中來去確定,前提是隻要物件不是一個 HashMap。那麼 javaType 應該被確定來保證使用正確型別處理器。

  • property:屬性名,即程式碼傳入的變數名。
  • javaType:該欄位在JAVA中的型別,比如int。
  • jdbcType:該欄位在JDBC中的型別,比如NUMERIC。
  • typeHandler:型別處理器
  • mode:引數型別為IN,OUT或INOUT引數
  • resultMap:結果。
#{property,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler,mode=OUT,resultMap=User}  

還好,MyBatis比較體諒我們,一般我們只需寫一個屬性名即可,如#{id},其他的如javaType和typeHandlerMybatis
會自動幫我們填好。可是這樣有時也會出問題,比如出現CLOB欄位時。
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。

#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
字串替換

預設情況下,使用#{}格式的語法會導致 MyBatis 建立預處理語句屬性並安全地設定值(比如?)。這樣做更安全,更迅速,通常也是首選做法,不過有時你只是想直接在 SQL 語句中插入一個不改變的字串。比如,像 ORDER BY,你可以這樣來使用:

ORDER BY ${columnName}

這裡 MyBatis 不會修改或轉義字串。

NOTE 以這種方式接受從使用者輸出的內容並提供給語句中不變的字串是不安全的,會導致潛在的 SQL 注入攻擊,因此要麼不允許使用者輸入這些欄位,要麼自行轉義並檢驗。

快取

MyBatis 包含一個非常強大的查詢快取特性,它可以非常方便地配置和定製。MyBatis 3 中的快取實現的很多改進都已經實現了,使得它更加強大而且易於配置。

預設情況下是沒有開啟快取的,除了區域性的 session 快取,可以增強變現而且處理迴圈 依賴也是必須的。要開啟二級快取,你需要在你的 SQL 對映檔案中新增一行:

<cache/>

字面上看就是這樣。這個簡單語句的效果如下:

  • 對映語句檔案中的所有 select 語句將會被快取。
  • 對映語句檔案中的所有 insert,update 和 delete 語句會重新整理快取。
  • 快取會使用 Least Recently Used(LRU,最近最少使用的)演算法來收回。
    根據時間表(比如 no Flush Interval,沒有重新整理間隔), 快取不會以任何時間順序 來重新整理。
  • 快取會儲存列表集合或物件(無論查詢方法返回什麼)的 1024 個引用。
  • 快取會被視為是 read/write(可讀/可寫)的快取,意味著物件檢索不是共享的,而 且可以安全地被呼叫者修改,而不干擾其他呼叫者或執行緒所做的潛在修改。

所有的這些屬性都可以通過快取元素的屬性來修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  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 框架中很多複雜的介面之一,但是簡單 給定它做什麼就行。

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 屬性來配置你的快取實現, 而且是通過 cache 元素來傳遞屬性, 比如, 下面程式碼會在你的快取實現中呼叫一個稱為 “setCacheFile(String file)” 的方法:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

你可以使用所有簡單型別作為 JavaBeans 的屬性,MyBatis 會進行轉換。

記得快取配置和快取例項是繫結在 SQL 對映檔案的名稱空間是很重要的。因此,所有 在相同名稱空間的語句正如繫結的快取一樣。 語句可以修改和快取互動的方式, 或在語句的 語句的基礎上使用兩種簡單的屬性來完全排除它們。預設情況下,語句可以這樣來配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

因為那些是預設的,你明顯不能明確地以這種方式來配置一條語句。相反,如果你想改 變預設的行為,只能設定 flushCache 和 useCache 屬性。比如,在一些情況下你也許想排除 從快取中查詢特定語句結果,或者你也許想要一個查詢語句來重新整理快取。相似地,你也許有 一些更新語句依靠執行而不需要重新整理快取。

參照快取

這個特殊名稱空間的唯一快取會被使用或者重新整理相同名稱空間內 的語句。也許將來的某個時候,你會想在名稱空間中共享相同的快取配置和例項。在這樣的 情況下你可以使用 cache-ref 元素來引用另外一個快取。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

相關文章