> 文檔出自:[https://mybatis.org/mybatis-3/zh/dynamic-sql.html](https://mybatis.org/mybatis-3/zh/dynamic-sql.html)
## Mybatis專題三:動態SQL
[TOC]
動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。
使用動態 SQL 并非一件易事,但借助可用于任何 SQL 映射語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。
如果你之前用過 JSTL 或任何基于類 XML 語言的文本處理器,你對動態 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間了解大量的元素。借助功能強大的基于 OGNL 的表達式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。
* if
* choose (when, otherwise)
* trim (where, set)
* foreach
### 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” 查找的情形。若兩者都沒有傳入,就返回標記為 featured 的 BLOG(這可能是管理員認為,與其返回大量的無意義隨機 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 問題。現在回到之前的 “if” 示例,這次我們將 “state = ‘ACTIVE’” 設置成動態條件,看看會發生什么。
~~~
<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’
~~~
這個查詢也會失敗。這個問題不能簡單地用條件元素來解決。這個問題是如此的難以解決,以至于解決過的人不會再想碰到這種問題。
MyBatis 有一個簡單且適合大多數場景的解決辦法。而在其他場景中,可以對其進行自定義以符合需求。而這,只需要一處簡單的改動:
~~~
<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*元素只會在子元素返回任何內容的情況下才插入 “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 關鍵字,并會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)。
來看看與*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 是值。
至此,我們已經完成了與 XML 配置及映射文件相關的討論。下一章將詳細探討 Java API,以便你能充分利用已經創建的映射配置。
### script
要在帶注解的映射器接口類中使用動態 SQL,可以使用*script*元素。比如:
~~~
@Update({"<script>",
"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}",
"</script>"})
void updateAuthorValues(Author author);
~~~
### bind
bind元素允許你在 OGNL 表達式以外創建一個變量,并將其綁定到當前的上下文。比如:
~~~
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
~~~
### 多數據庫支持
如果配置了 databaseIdProvider,你就可以在動態代碼中使用名為 “\_databaseId” 的變量來為不同的數據庫構建特定的語句。比如下面的例子:
~~~
<insert id="insert">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
<if test="_databaseId == 'oracle'">
select seq_users.nextval from dual
</if>
<if test="_databaseId == 'db2'">
select nextval for seq_users from sysibm.sysdummy1"
</if>
</selectKey>
insert into users values (#{id}, #{name})
</insert>
~~~
### 動態 SQL 中的插入腳本語言
MyBatis 從 3.2 版本開始支持插入腳本語言,這允許你插入一種語言驅動,并基于這種語言來編寫動態 SQL 查詢語句。
可以通過實現以下接口來插入一種語言:
~~~
public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
~~~
實現自定義語言驅動后,你就可以在 mybatis-config.xml 文件中將它設置為默認語言:
~~~
<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>
~~~
或者,你也可以使用lang屬性為特定的語句指定語言:
~~~
<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>
~~~
或者,在你的 mapper 接口上添加@Lang注解:
~~~
public interface Mapper {
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List<Blog> selectBlog();
}
~~~
- JavaCook
- Java專題零:類的繼承
- Java專題一:數據類型
- Java專題二:相等與比較
- Java專題三:集合
- Java專題四:異常
- Java專題五:遍歷與迭代
- Java專題六:運算符
- Java專題七:正則表達式
- Java專題八:泛型
- Java專題九:反射
- Java專題九(1):反射
- Java專題九(2):動態代理
- Java專題十:日期與時間
- Java專題十一:IO與NIO
- Java專題十一(1):IO
- Java專題十一(2):NIO
- Java專題十二:網絡
- Java專題十三:并發編程
- Java專題十三(1):線程與線程池
- Java專題十三(2):線程安全與同步
- Java專題十三(3):內存模型、volatile、ThreadLocal
- Java專題十四:JDBC
- Java專題十五:日志
- Java專題十六:定時任務
- Java專題十七:JavaMail
- Java專題十八:注解
- Java專題十九:淺拷貝與深拷貝
- Java專題二十:設計模式
- Java專題二十一:序列化與反序列化
- 附加專題一:MySQL
- MySQL專題零:簡介
- MySQL專題一:安裝與連接
- MySQL專題二:DDL與DML語法
- MySQL專題三:工作原理
- MySQL專題四:InnoDB存儲引擎
- MySQL專題五:sql優化
- MySQL專題六:數據類型
- 附加專題二:Mybatis
- Mybatis專題零:簡介
- Mybatis專題一:配置文件
- Mybatis專題二:映射文件
- Mybatis專題三:動態SQL
- Mybatis專題四:源碼解析
- 附加專題三:Web編程
- Web專題零:HTTP協議
- Web專題一:Servlet
- Web專題二:Cookie與Session
- 附加專題四:Redis
- Redis專題一:數據類型
- Redis專題二:事務
- Redis專題三:key的過期
- Redis專題四:消息隊列
- Redis專題五:持久化