動态 sql
mybatis 的強大特性之一便是它的動态 sql。如果你有使用 jdbc 或其他類似架構的經驗,你就能體會到根據不同條件拼接 sql 語句有多麼痛苦。拼接的時候要確定不能忘了必要的空格,還要注意省掉一連串列名最後的逗号。利用動态 sql 這一特性可以徹底擺脫這種痛苦。
通常使用動态 sql 不可能是獨立的一部分,mybatis 通過一種強大的動态 sql 語言明顯地改進了這種情形,這種語言可以被用在任意的 sql 映射語句中。
<code>if choose (when, otherwise) trim (where, set) foreach</code>
if
動态 sql 通常要做的事情是有條件地包含 where 子句的一部分。比如:
<code><select id="findactiveblogwithtitlelike" resulttype="blog"> select * from blog where state = ‘active’ <if test="title != null"> and title like #{title} </if> </select></code>
這條語句提供了一個可選的文本查找類型的功能。如果沒有傳入“title”,那麼所有處于“active”狀态的blog都會傳回;反之若傳入了“title”,則會模糊查找“title”内容的blog來傳回(就這個例子而言,細心的讀者會發現其中的參數值是可以包含一些掩碼或通配符的)。
如果想讓“title”和“author”兩個條件進行可選搜尋呢?首先,改變語句的名稱讓它更具實際意義;然後隻要加入另一個條件即可。
<code><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></code>
choose, when, otherwise
有些時候,我們不想用到所有的條件語句,而隻想從中擇其一二。針對這種情況,mybatis 提供了 choose 元素,它有點像 java 中的 switch 語句。
還是上面的例子,但是這次變為提供了“title”就按“title”查找,提供了“author”就按“author”查找,若兩者都沒有提供,就傳回所有符合條件的blog(實際情況可能是由管理者按一定政策選出blog清單,而不是傳回大量無意義的随機結果)。
<code><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></code>
trim, where, set
前面的例子已經合宜地解決了一個臭名昭著的動态 sql 問題。現在考慮回到“if”示例,但這次我們将“active = 1”也設定成一個動态條件,将會發生什麼情況。
<code><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></code>
如果這些條件沒有一個能比對上将會怎樣?最終這條 sql 會變成這樣:
<code>select * from blog where</code>
這會導緻查詢失敗。如果僅僅第二個條件比對又會怎樣?這條 sql 最終會是這樣:
<code>select * from blog where and title like ‘sometitle’</code>
這個查詢也會失敗。這個問題不能簡單的用條件句式來解決,如果你也曾經被迫這樣寫過,那麼你很可能從此以後都不想再這樣去寫了。
mybatis 有一個簡單的方案,在90%的情況下都會生效。同時你可以用自定義方式來處理不生效的情況。隻需簡單的更改,一切即可正常運作:
<code><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></code>
where 元素隻在至少一個 if條件符合的情況下才去插入“where”子句。而且,如果内容是“and”或“or”開頭的,where 元素便會将他們去除。
如果 where 元素沒有按你想的準确執行,你還可以通過自定義 trim 元素來定制你想要的功能。比如,和 where 元素等價的trim 元素為:
<code><trim prefix="where" prefixoverrides="and |or "> ... </trim></code>
prefixoverrides 屬性會忽略通過管道分隔的文本序列(注意此例中的空格也是必要的)。即移除 prefixoverrides 屬性中指定的内容,同時插入 prefix 屬性中的所有内容。
類似的用于動态更新語句的解決方案叫做 set。set 元素可以被用于動态包含需要更新的列,而省略其他的。比如:
<code><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></code>
這裡,set 元素會動态前置 set 關鍵字,同時也會消除額外的逗号,因為用了條件語句之後很可能就會在生成的指派語句的後面留下這些逗号。
若你對等價的自定義 trim 元素的樣子感興趣,這就是:
<code><trim prefix="set" suffixoverrides=","> ... </trim></code>
注意這裡我們忽略的是字尾中的值,而又一次附加了字首中的值。
foreach
動态 sql 的另外一個常用的必要操作是需要對一個集合進行周遊,通常是在建構 in 條件語句的時候。比如:
<code><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></code>
foreach 元素的功能是非常強大的,它允許你指定一個集合,聲明可以用在元素體内的集合項和索引變量。它也允許你指定開閉比對的字元串以及在疊代中間放置分隔符。這個元素是很智能的,是以它不會偶然地附加多餘的分隔符。
注意 你可以将任何可疊代對象(如清單、集合等)和任何的字典或者數組對象傳遞給foreach作為集合參數。當使用可疊代對象或者數組時,index是目前疊代的次數,item的值是本次疊代擷取的元素。當使用字典(或者map.entry對象的集合)時,index是鍵,item是值。
到此我們已經完成了涉及 xml 配置檔案和 xml 映射檔案的讨論。下一部分将詳細探讨 java api,這樣才能從已建立的映射中擷取最大利益。
bind
bind 元素可以從 ognl 表達式中建立一個變量并将其綁定到上下文。比如:
<code><select id="selectblogslike" resulttype="blog"> <bind name="pattern" value="'%' + _parameter.gettitle() + '%'" /> select * from blog where title like #{pattern} </select> multi-db vendor support</code>
一個配置了“_databaseid”變量的 databaseidprovider 對于動态代碼來說是可用的,這樣就可以根據不同的資料庫廠商建構特定的語句。比如下面的例子:
<code><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></code>
動态 sql 中可插拔的腳本語言
mybatis 從 3.2 開始支援可插拔的腳本語言,是以你可以在插入一種語言的驅動(language driver)之後來寫基于這種語言的動态 sql 查詢。
可以通過實作下面接口的方式來插入一種語言:
<code>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); }</code>
一旦有了自定義的語言驅動,你就可以在 mybatis-config.xml 檔案中将它設定為預設語言:
<code><typealiases> <typealias type="org.sample.mylanguagedriver" alias="mylanguage"/> </typealiases> <settings> <setting name="defaultscriptinglanguage" value="mylanguage"/> </settings></code>
除了設定預設語言,你也可以針對特殊的語句指定特定語言,這可以通過如下的 lang 屬性來完成:
<code><select id="selectblog" lang="mylanguage"> select * from blog </select></code>
或者在你正在使用的映射中加上注解 @lang 來完成:
<code>public interface mapper { @lang(mylanguagedriver.class) @select("select * from blog") list<blog> selectblog(); }</code>
注意 可以将 apache velocity 作為動态語言來使用,更多細節請參考 mybatis-velocity 項目。
你前面看到的所有 xml 标簽都是預設 mybatis 語言提供的,它是由别名為 xml 語言驅動器 org.apache.ibatis.scripting.xmltags.xmllanguagedriver 驅動的。