MyBatis 的強(qiáng)大特性之一便是它的動(dòng)態(tài) SQL。如果你有使用 JDBC 或其他類(lèi)似框架的經(jīng)驗(yàn),你就能體會(huì)到根據(jù)不同條件拼接 SQL 語(yǔ)句有多么痛苦。拼接的時(shí)候要確保不能忘了必要的空格,還要注意省掉列名列表最后的逗號(hào)。利用動(dòng)態(tài) SQL 這一特性可以徹底擺脫這種痛苦。
通常使用動(dòng)態(tài) SQL 不可能是獨(dú)立的一部分,MyBatis 當(dāng)然使用一種強(qiáng)大的動(dòng)態(tài) SQL 語(yǔ)言來(lái)改進(jìn)這種情形,這種語(yǔ)言可以被用在任意的 SQL 映射語(yǔ)句中。
動(dòng)態(tài) SQL 元素和使用 JSTL 或其他類(lèi)似基于 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多的元素需要來(lái)了解。MyBatis 3 大大提升了它們,現(xiàn)在用不到原先一半的元素就可以了。MyBatis 采用功能強(qiáng)大的基于 OGNL 的表達(dá)式來(lái)消除其他元素。
動(dòng)態(tài) SQL 通常要做的事情是有條件地包含 where 子句的一部分。比如:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
這條語(yǔ)句提供了一個(gè)可選的文本查找類(lèi)型的功能。如果沒(méi)有傳入"title",那么所有處于"ACTIVE"狀態(tài)的BLOG都會(huì)返回;反之若傳入了"title",那么就會(huì)把模糊查找"title"內(nèi)容的BLOG結(jié)果返回(就這個(gè)例子而言,細(xì)心的讀者會(huì)發(fā)現(xiàn)其中的參數(shù)值是可以包含一些掩碼或通配符的)。
如果想可選地通過(guò)"title"和"author"兩個(gè)條件搜索該怎么辦呢?首先,改變語(yǔ)句的名稱(chēng)讓它更具實(shí)際意義;然后只要加入另一個(gè)條件即可。
<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>
有些時(shí)候,我們不想用到所有的條件語(yǔ)句,而只想從中擇其一二。針對(duì)這種情況,MyBatis 提供了 choose 元素,它有點(diǎn)像 Java 中的 switch 語(yǔ)句。
還是上面的例子,但是這次變?yōu)樘峁┝?title"就按"title"查找,提供了"author"就按"author"查找,若兩者都沒(méi)有提供,就返回所有符合條件的BLOG(實(shí)際情況可能是由管理員按一定策略選出BLOG列表,而不是返回大量無(wú)意義的隨機(jī)結(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>
前面幾個(gè)例子已經(jīng)合宜地解決了一個(gè)臭名昭著的動(dòng)態(tài) SQL 問(wèn)題?,F(xiàn)在考慮回到"if"示例,這次我們將"ACTIVE = 1"也設(shè)置成動(dòng)態(tài)的條件,看看會(huì)發(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>
如果這些條件沒(méi)有一個(gè)能匹配上將會(huì)怎樣?最終這條 SQL 會(huì)變成這樣:
SELECT * FROM BLOG
WHERE
這會(huì)導(dǎo)致查詢失敗。如果僅僅第二個(gè)條件匹配又會(huì)怎樣?這條 SQL 最終會(huì)是這樣:
SELECT * FROM BLOG
WHERE
AND title like 'someTitle'
這個(gè)查詢也會(huì)失敗。這個(gè)問(wèn)題不能簡(jiǎn)單的用條件句式來(lái)解決,如果你也曾經(jīng)被迫這樣寫(xiě)過(guò),那么你很可能從此以后都不想再這樣去寫(xiě)了。
MyBatis 有一個(gè)簡(jiǎn)單的處理,這在90%的情況下都會(huì)有用。而在不能使用的地方,你可以自定義處理方式來(lái)令其正常工作。一處簡(jiǎn)單的修改就能得到想要的效果:
<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 元素知道只有在一個(gè)以上的if條件有值的情況下才去插入"WHERE"子句。而且,若最后的內(nèi)容是"AND"或"OR"開(kāi)頭的,where 元素也知道如何將他們?nèi)コ?/p>
如果 where 元素沒(méi)有按正常套路出牌,我們還是可以通過(guò)自定義 trim 元素來(lái)定制我們想要的功能。比如,和 where 元素等價(jià)的自定義 trim 元素為:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 屬性會(huì)忽略通過(guò)管道分隔的文本序列(注意此例中的空格也是必要的)。它帶來(lái)的結(jié)果就是所有在 prefixOverrides 屬性中指定的內(nèi)容將被移除,并且插入 prefix 屬性中指定的內(nèi)容。
類(lèi)似的用于動(dòng)態(tài)更新語(yǔ)句的解決方案叫做 set。set 元素可以被用于動(dòng)態(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 元素會(huì)動(dòng)態(tài)前置 SET 關(guān)鍵字,同時(shí)也會(huì)消除無(wú)關(guān)的逗號(hào),因?yàn)橛昧藯l件語(yǔ)句之后很可能就會(huì)在生成的賦值語(yǔ)句的后面留下這些逗號(hào)。
若你對(duì)等價(jià)的自定義 trim 元素的樣子感興趣,那這就應(yīng)該是它的真面目:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意這里我們忽略的是后綴中的值,而又一次附加了前綴中的值。
動(dòng)態(tài) SQL 的另外一個(gè)常用的必要操作是需要對(duì)一個(gè)集合進(jìn)行遍歷,通常是在構(gòu)建 IN 條件語(yǔ)句的時(shí)候。比如:
<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 元素的功能是非常強(qiáng)大的,它允許你指定一個(gè)集合,聲明可以用在元素體內(nèi)的集合項(xiàng)和索引變量。它也允許你指定開(kāi)閉匹配的字符串以及在迭代中間放置分隔符。這個(gè)元素是很智能的,因此它不會(huì)偶然地附加多余的分隔符。
注意 你可以將一個(gè) List 實(shí)例或者數(shù)組作為參數(shù)對(duì)象傳給 MyBatis,當(dāng)你這么做的時(shí)候,MyBatis 會(huì)自動(dòng)將它包裝在一個(gè) Map 中并以名稱(chēng)為鍵。List 實(shí)例將會(huì)以"list"作為鍵,而數(shù)組實(shí)例的鍵將是"array"。
到此我們已經(jīng)完成了涉及 XML 配置文件和 XML 映射文件的討論。下一部分將詳細(xì)探討 Java API,這樣才能從已創(chuàng)建的映射中獲取最大利益。
bind
元素可以從 OGNL 表達(dá)式中創(chuàng)建一個(gè)變量并將其綁定到上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
一個(gè)配置了"_databaseId"變量的 databaseIdProvider 對(duì)于動(dòng)態(tài)代碼來(lái)說(shuō)是可用的,這樣就可以根據(jù)不同的數(shù)據(jù)庫(kù)廠商構(gòu)建特定的語(yǔ)句。比如下面的例子:
<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 開(kāi)始支持可插拔的腳本語(yǔ)言,因此你可以在插入一種語(yǔ)言的驅(qū)動(dòng)(language driver)之后來(lái)寫(xiě)基于這種語(yǔ)言的動(dòng)態(tài) SQL 查詢。
可以通過(guò)實(shí)現(xiàn)下面接口的方式來(lái)插入一種語(yǔ)言:
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);
}
一旦有了自定義的語(yǔ)言驅(qū)動(dòng),你就可以在 mybatis-config.xml 文件中將它設(shè)置為默認(rèn)語(yǔ)言:
<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>
除了設(shè)置默認(rèn)語(yǔ)言,你也可以針對(duì)特殊的語(yǔ)句指定特定語(yǔ)言,這可以通過(guò)如下的 lang
屬性來(lái)完成:
<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>
或者在你正在使用的映射中加上注解 @Lang
來(lái)完成:
public interface Mapper {
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List selectBlog();
}
注意 可以將 Apache Velocity 作為動(dòng)態(tài)語(yǔ)言來(lái)使用,更多細(xì)節(jié)請(qǐng)參考 MyBatis-Velocity 項(xiàng)目。
你前面看到的所有 xml 標(biāo)簽都是默認(rèn) MyBatis 語(yǔ)言提供的,它是由別名為 xml
語(yǔ)言驅(qū)動(dòng)器 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver
驅(qū)動(dòng)的。
更多建議: