MyBatis 的強大特性之一即是它的動態 SQL。若是你有使用 JDBC 或其它相似框架的經驗,你就能體會到根據不一樣條件拼接 SQL 語句的痛苦。例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL 這一特性能夠完全擺脫這種痛苦。數據庫
雖然在之前使用動態 SQL 並不是一件易事,但正是 MyBatis 提供了能夠被用在任意 SQL 映射語句中的強大的動態 SQL 語言得以改進這種情形。apache
動態 SQL 元素和 JSTL 或基於相似 XML 的文本處理器類似。在 MyBatis 以前的版本中,有不少元素須要花時間瞭解。MyBatis 3 大大精簡了元素種類,如今只需學習原來一半的元素即可。MyBatis 採用功能強大的基於 OGNL 的表達式來淘汰其它大部分元素。數組
動態 SQL 一般要作的事情是根據條件包含 where 子句的一部分。好比:mybatis
<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」參數值是能夠包含一些掩碼或通配符的)。oracle
若是但願經過「title」和「author」兩個參數進行可選搜索該怎麼辦呢?首先,改變語句的名稱讓它更具實際意義;而後只要加入另外一個條件便可。app
<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>
有時咱們不想應用到全部的條件語句,而只想從中擇其一項。針對這種狀況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。框架
仍是上面的例子,可是此次變爲提供了「title」就按「title」查找,提供了「author」就按「author」查找的情形,若二者都沒有提供,就返回全部符合條件的 BLOG(實際狀況多是由管理員按必定策略選出 BLOG 列表,而不是返回大量無心義的隨機結果)。dom
<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>
前面幾個例子已經合宜地解決了一個臭名昭著的動態 SQL 問題。如今回到「if」示例,此次咱們將「ACTIVE = 1」也設置成動態的條件,看看會發生什麼。ide
<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 有一個簡單的處理,這在 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>
注意這裏咱們刪去的是後綴值,同時添加了前綴值。
動態 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 配置文件和 XML 映射文件的討論。下一章將詳細探討 Java API,這樣就能提升已建立的映射文件的利用效率。
bind 元素能夠從 OGNL 表達式中建立一個變量並將其綁定到上下文。好比:
<select id="selectBlogsLike" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT * FROM BLOG WHERE title LIKE #{pattern} </select>
一個配置了「_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>
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(); }
注意 能夠將 Apache Velocity 做爲動態語言來使用,更多細節請參考 MyBatis-Velocity 項目。
你前面看到的全部 xml 標籤都是由默認 MyBatis 語言提供的,而它由別名爲 xml 的語言驅動器 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver 所提供。