動態(tài) SQL
MyBatis 的強大特性之一便是它的動態(tài) SQL。如果你有使用 JDBC 或其它類似框架的經(jīng)驗,你就能體會到根據(jù)不同條件拼接 SQL 語句的痛苦沮明。例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態(tài) SQL 這一特性可以徹底擺脫這種痛苦宰僧。
雖然在以前使用動態(tài) SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射語句中的強大的動態(tài) SQL 語言得以改進(jìn)這種情形观挎。
動態(tài) SQL 元素和 JSTL 或基于類似 XML 的文本處理器相似琴儿。在 MyBatis 之前的版本中,有很多元素需要花時間了解嘁捷。MyBatis 3 大大精簡了元素種類造成,現(xiàn)在只需學(xué)習(xí)原來一半的元素便可。MyBatis 采用功能強大的基于 OGNL 的表達(dá)式來淘汰其它大部分元素雄嚣。
if
choose (when, otherwise)
trim (where, set)
foreach
動態(tài) SQL 通常要做的事情是根據(jù)條件包含 where 子句的一部分晒屎。比如:
<select id="findActiveBlogWithTitleLike"? ? resultType="Blog">? SELECT * FROM BLOG
? WHERE state = ‘ACTIVE’
? <if test="title != null">? ? AND title like #{title}
? </if></select>
這條語句提供了一種可選的查找文本功能。如果沒有傳入“title”,那么所有處于“ACTIVE”狀態(tài)的BLOG都會返回鼓鲁;反之若傳入了“title”履肃,那么就會對“title”一列進(jìn)行模糊查找并返回 BLOG 結(jié)果(細(xì)心的讀者可能會發(fā)現(xiàn),“title”參數(shù)值是可以包含一些掩碼或通配符的)坐桩。
如果希望通過“title”和“author”兩個參數(shù)進(jìn)行可選搜索該怎么辦呢尺棋?首先,改變語句的名稱讓它更具實際意義绵跷;然后只要加入另一個條件即可膘螟。
<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>
有時我們不想應(yīng)用到所有的條件語句,而只想從中擇其一項碾局。針對這種情況荆残,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句净当。
還是上面的例子内斯,但是這次變?yōu)樘峁┝恕皌itle”就按“title”查找,提供了“author”就按“author”查找的情形像啼,若兩者都沒有提供俘闯,就返回所有符合條件的 BLOG(實際情況可能是由管理員按一定策略選出 BLOG 列表,而不是返回大量無意義的隨機結(jié)果)忽冻。
<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>
前面幾個例子已經(jīng)合宜地解決了一個臭名昭著的動態(tài) SQL 問題≌胬剩現(xiàn)在回到“if”示例,這次我們將“ACTIVE = 1”也設(shè)置成動態(tài)的條件僧诚,看看會發(fā)生什么遮婶。
<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>
如果這些條件沒有一個能匹配上會發(fā)生什么?最終這條 SQL 會變成這樣:
SELECT * FROM BLOG
WHERE
這會導(dǎo)致查詢失敗湖笨。如果僅僅第二個條件匹配又會怎樣旗扑?這條 SQL 最終會是這樣:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
這個查詢也會失敗。這個問題不能簡單地用條件句式來解決慈省,如果你也曾經(jīng)被迫這樣寫過臀防,那么你很可能從此以后都不會再寫出這種語句了。
MyBatis 有一個簡單的處理辫呻,這在 90% 的情況下都會有用清钥。而在不能使用的地方,你可以自定義處理方式來令其正常工作放闺。一處簡單的修改就能達(dá)到目的:
<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?元素也會將它們?nèi)コ?/p>
如果?where?元素沒有按正常套路出牌,我們可以通過自定義 trim 元素來定制?where?元素的功能。比如匾寝,和?where?元素等價的自定義 trim 元素為:
<trim prefix="WHERE" prefixOverrides="AND |OR ">? ...</trim>
prefixOverrides?屬性會忽略通過管道分隔的文本序列(注意此例中的空格也是必要的)搬葬。它的作用是移除所有指定在?prefixOverrides?屬性中的內(nèi)容,并且插入?prefix?屬性中指定的內(nèi)容艳悔。
類似的用于動態(tài)更新語句的解決方案叫做?set急凰。set?元素可以用于動態(tài)包含需要更新的列,而舍去其它的猜年。比如:
<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?元素會動態(tài)前置 SET 關(guān)鍵字,同時也會刪掉無關(guān)的逗號乔外,因為用了條件語句之后很可能就會在生成的 SQL 語句的后面留下這些逗號床三。(譯者注:因為用的是“if”元素,若最后一個“if”沒有匹配上而前面的匹配上杨幼,SQL 語句的最后就會有一個逗號遺留)
若你對?set?元素等價的自定義 trim 元素的代碼感興趣撇簿,那這就是它的真面目:
<trim prefix="SET" suffixOverrides=",">? ...</trim>
注意這里我們刪去的是后綴值,同時添加了前綴值差购。
動態(tài) SQL 的另外一個常用的操作需求是對一個集合進(jìn)行遍歷四瘫,通常是在構(gòu)建 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?元素的功能非常強大欲逃,它允許你指定一個集合莲组,聲明可以在元素體內(nèi)使用的集合項(item)和索引(index)變量。它也允許你指定開頭與結(jié)尾的字符串以及在迭代結(jié)果之間放置分隔符暖夭。這個元素是很智能的锹杈,因此它不會偶然地附加多余的分隔符。
注意?你可以將任何可迭代對象(如 List迈着、Set 等)竭望、Map 對象或者數(shù)組對象傳遞給?foreach?作為集合參數(shù)。當(dāng)使用可迭代對象或者數(shù)組時裕菠,index 是當(dāng)前迭代的次數(shù)咬清,item 的值是本次迭代獲取的元素。當(dāng)使用 Map 對象(或者 Map.Entry 對象的集合)時奴潘,index 是鍵旧烧,item 是值。
到此我們已經(jīng)完成了涉及 XML 配置文件和 XML 映射文件的討論画髓。下一章將詳細(xì)探討 Java API掘剪,這樣就能提高已創(chuàng)建的映射文件的利用效率。
bind?元素可以從 OGNL 表達(dá)式中創(chuàng)建一個變量并將其綁定到上下文奈虾。比如:
<select id="selectBlogsLike" resultType="Blog">? <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />? SELECT * FROM BLOG
? WHERE title LIKE #{pattern}</select>
一個配置了“_databaseId”變量的 databaseIdProvider 可用于動態(tài)代碼中夺谁,這樣就可以根據(jù)不同的數(shù)據(jù)庫廠商構(gòu)建特定的語句廉赔。比如下面的例子:
<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>
MyBatis 從 3.2 開始支持可插拔腳本語言,這允許你插入一種腳本語言驅(qū)動匾鸥,并基于這種語言來編寫動態(tài) SQL 查詢語句蜡塌。
可以通過實現(xiàn)以下接口來插入一種語言:
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);}
一旦設(shè)定了自定義語言驅(qū)動,你就可以在 mybatis-config.xml 文件中將它設(shè)置為默認(rèn)語言:
<typeAliases>? <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/></typeAliases><settings>? <setting name="defaultScriptingLanguage" value="myLanguage"/></settings>
除了設(shè)置默認(rèn)語言勿负,你也可以針對特殊的語句指定特定語言馏艾,可以通過如下的?lang?屬性來完成:
<select id="selectBlog" lang="myLanguage">? SELECT * FROM BLOG</select>
或者,如果你使用的是映射器接口類奴愉,在抽象方法上加上?@Lang?注解即可:
public interface Mapper {? @Lang(MyLanguageDriver.class)? @Select("SELECT * FROM BLOG")? List<Blog> selectBlog();}
注意?可以將 Apache Velocity 作為動態(tài)語言來使用琅摩,更多細(xì)節(jié)請參考 MyBatis-Velocity 項目。