前言:
在以前我們使用JDBC或者其它框架時,一件很痛苦的事情是根據不同條件拼接 SQL 語句。拼接的時候要確保不能忘了必要的空格,還要注意省掉列名列表最後的逗號。然而現在,我們利用動態 SQL 這一特性就可以徹底擺脫這種痛苦。
通常使用動態SQL不可能是獨立的一部分,MyBatis當然使用一種強大的動態SQL語言來改進這種情形,這種語言可以被用在任意映射的SQL語句中。
動態SQL元素和使用JSTL或其它相似的基於XML的文本處理器相似,在MyBatis之前的版本中,有很多元素需要了解,MyBatis3大大地提升了它們,現在用不到原先一半的元素就能工作了,MyBatis採用功能強大的基於OGNL的表達式來消除其他元素。
if:
動態 SQL 通常要做的事情是有條件地包含 where 子句的一部分。比如:
<select id="findBlog"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
這條語句提供了一個可選的文本查找類型的功能。如果沒有傳入“title”,那麼所有處於“ACTIVE”狀態的BLOG都會返回;反之若傳入了“title”,那麼就會把模糊查找“title”內容的BLOG結果返回(就這個例子而言,細心的讀者會發現其中的參數值是可以包含一些掩碼或通配符的)。
多個條件查詢:
<select id="findBlog"
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:
和Java中的switch…case…類似,MyBasit提供choose元素。
<select id="findBlog"
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>
兩個when只能滿足一個,都不滿足則走other。
trim, where, set:
第一個例子已經示例了if的用法,但是這種用法有個缺陷—-動態SQL外必須有where子句。
什麼意思,因爲很多時候我們需要where後面的子句都動態生成,而不是事先有一個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>
</select>
如果這些條件沒有一個能匹配上將會怎樣?最終這條 SQL 會變成這樣:
SELECT * FROM BLOG WHERE
這樣會導致查詢失敗。如果僅僅是匹配第二個條件,這條SQL最終會是這樣:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
這個查詢也會失敗。這個問題不能簡單的用條件句式來解決。
這裏有兩種方法解決這種問題:
1)一個討巧的辦法是用where 1 = 1的方式
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE 1 = 1
<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>
</select>
解釋:因爲”1 = 1”永遠滿足,所以相當於給where加了一層true而已,此時動態SQL生成什麼where判斷條件就是什麼。
2)MyBatis 有一個簡單的處理,這在90%的情況下都會有用。
<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 元素知道只有在一個以上的if條件有值的情況下才去插入“WHERE”子句。而且,若最後的內容是“AND”或“OR”開頭的,where 元素也知道如何將他們去除。
如果 where 元素沒有按正常套路出牌,我們還是可以通過自定義 trim 元素來定製我們想要的功能。比如,和 where 元素等價的自定義 trim 元素爲:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
…
</trim>
即:
```sql
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<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>
</trim>
</select>
注:prefixOverrides 屬性會忽略通過管道分隔的文本序列(注意此例中的空格也是必要的)。
類似的用於動態更新語句的解決方案叫做 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 關鍵字,同時也會消除無關的逗號,因爲用了條件語句之後很可能就會在生成的賦值語句的後面留下這些逗號。
若你對等價的自定義 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 元素的功能是非常強大的,它允許你指定一個集合,聲明可以用在元素體內的集合項和索引變量。它也允許你指定開閉匹配的字符串以及在迭代中間放置分隔符。這個元素是很智能的,因此它不會偶然地附加多餘的分隔符。
注:可以將任何可迭代對象(如列表、集合等)和任何的字典或者數組對象傳遞給foreach作爲集合參數。當使用可迭代對象或者數組時,index是當前迭代的次數,item的值是本次迭代獲取的元素。當使用字典(或者Map.Entry對象的集合)時,index是鍵,item是值。