MyBatis之動態SQL
本文內容來自Mybatis文檔,在此整理分享給大家。
MyBatis 的強大特性之一便是它的動態 SQL。
1 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”和“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>
2 choose, when, otherwise
有時我們不想應用到所有的條件語句,而只想從中擇其一項。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。
還是上面的例子,但是這次變爲提供了“title”就按“title”查找,提供了“author”就按“author”查找的情形,若兩者都沒有提供,就返回所有符合條件的 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>
3 trim, where, set
前面幾個例子已經合宜地解決了一個臭名昭著的動態 SQL 問題。現在回到“if”示例,這次我們將“ACTIVE = 1”也設置成動態的條件,看看會發生什麼。
<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
這個查詢也會失敗。這個問題不能簡單地用條件句式來解決。
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 元素只會在至少有一個子元素的條件返回 SQL 子句的情況下才去插入“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 關鍵字,同時也會刪掉無關的逗號,因爲用了條件語句之後很可能就會在生成的 SQL 語句的後面留下這些逗號。(譯者注:因爲用的是“if”元素,若最後一個“if”沒有匹配上而前面的匹配上,SQL 語句的最後就會有一個逗號遺留)
若你對 set 元素等價的自定義 trim 元素的代碼感興趣,那這就是它的真面目:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意這裏我們刪去的是後綴值,同時添加了前綴值。
3 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 是值。
4 bind
bind 元素可以從 OGNL 表達式中創建一個變量並將其綁定到上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
5 多數據庫支持
一個配置了“_databaseId”變量的 databaseIdProvider 可用於動態代碼中,這樣就可以根據不同的數據庫廠商構建特定的語句。比如下面的例子:
<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>
6 動態 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>
或者,如果你使用的是映射器接口類,在抽象方法上加上 @Lang 註解即可:
public interface Mapper {
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List<Blog> selectBlog();
}