位置:首頁 > Java技術 > MyBatis教學 > MyBatis動態SQL語句

MyBatis動態SQL語句

MyBatis 的強大特性之一便是它的動態 SQL。如果你有使用 JDBC 或其他類似框架的經驗,你就能體會到根據不同條件拚接 SQL 語句有多麼痛苦。拚接的時候要確保不能忘了必要的空格,還要注意省掉列名列表最後的逗號。利用動態 SQL 這一特性可以徹底擺脫這種痛苦。

通常使用動態 SQL 不可能是獨立的一部分,MyBatis 當然使用一種強大的動態 SQL 語言來改進這種情形,這種語言可以被用在任意的 SQL 映射語句中。

動態 SQL 元素和使用 JSTL 或其他類似基於 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多的元素需要來了解。MyBatis 3 大大提升了它們,現在用不到原先一半的元素就可以了。MyBatis 采用功能強大的基於 OGNL 的表達式來消除其他元素。

mybatis 的動態sql語句是基於OGNL表達式的。可以方便的在 sql 語句中實現某些邏輯. 總體說來mybatis 動態SQL 語句主要有以下幾類:
1. if 語句 (簡單的條件判斷)
2. choose (when,otherwize) ,相當於java 語言中的 switch ,與 jstl 中的choose 很類似.
3. trim (對包含的內容加上 prefix,或者 suffix 等,前綴,後綴)
4. where (主要是用來簡化sql語句中where條件判斷的,能智能的處理 and or ,不必擔心多餘導致語法錯誤)
5. set (主要用於更新時)
6. foreach (在實現 mybatis in 語句查詢時特彆有用)

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>

choose, when, otherwise

有些時候,我們不想用到所有的條件語句,而隻想從中擇其一二。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。

還是上麵的例子,但是這次變為提供了“title”就按“title”查找,提供了“author”就按“author”查找,若兩者都冇有提供,就返回所有符合條件的BLOG(實際情況可能是由管理員按一定策略選出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>

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

這會導致查詢失敗。如果僅僅第二個條件匹配又會怎樣?這條 SQL 最終會是這樣:

SELECT * FROM BLOG
WHERE 
AND title like gitbook.net

這個查詢也會失敗。這個問題不能簡單的用條件句式來解決,如果你也曾經被迫這樣寫過,那麼你很可能從此以後都不想再這樣去寫了。

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 元素知道隻有在一個以上的if條件有值的情況下才去插入“WHERE”子句。而且,若最後的內容是“AND”或“OR”開頭的,where 元素也知道如何將他們去除。

如果 where 元素冇有按正常套路出牌,我們還是可以通過自定義 trim 元素來定製我們想要的功能。比如,和 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 關鍵字,同時也會消除無關的逗號,因為用了條件語句之後很可能就會在生成的賦值語句的後麵留下這些逗號。

若你對等價的自定義 trim 元素的樣子感興趣,那這就應該是它的真麵目:

<trim prefix="SET" suffixOverrides=","> ... </trim>

注意這裡我們忽略的是後綴中的值,而又一次附加了前綴中的值。

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 元素的功能是非常強大的,它允許你指定一個集合,聲明可以用在元素體內的集合項和索引變量。它也允許你指定開閉匹配的字符串以及在迭代中間放置分隔符。這個元素是很智能的,因此它不會偶然地附加多餘的分隔符。

注意 你可以將一個 List 實例或者數組作為參數對象傳給 MyBatis,當你這麼做的時候,MyBatis 會自動將它包裝在一個 Map 中並以名稱為鍵。List 實例將會以“list”作為鍵,而數組實例的鍵將是“array”。

到此我們已經完成了涉及 XML 配置文件和 XML 映射文件的討論。下一部分將詳細探討 Java API,這樣才能從已創建的映射中獲取最大利益。

bind

bind 元素可以從 OGNL 表達式中創建一個變量並將其綁定到上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

Multi-db vendor support

一個配置了“_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>

動態 SQL 中可插拔的腳本語言

MyBatis 從 3.2 開始支持可插拔的腳本語言,因此你可以在插入一種語言的驅動(language driver)之後來寫基於這種語言的動態 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();
}