天天看點

070_Mybatis動态SQL

目錄

  • 動态SQL
  • if
  • choose、when、otherwise
  • trim、where、set
  • foreach
  • SQL片段

動态 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的架構,你應該能了解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確定不能忘記添加必要的空格,還要注意去掉清單最後一個列名的逗号。利用動态 SQL,可以徹底擺脫這種痛苦。

使用動态 SQL 并非一件易事,但借助可用于任何 SQL 映射語句中的強大的動态 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。

如果你之前用過 JSTL 或任何基于類 XML 語言的文本處理器,你對動态 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間了解大量的元素。借助功能強大的基于 OGNL 的表達式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。

  • choose (when, otherwise)
  • trim (where, set)

使用動态 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” 的參數值需要包含查找掩碼或通配符字元)。

如果希望通過 “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>
           

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

還是上面的例子,但是政策變為:傳入了 “title” 就按 “title” 查找,傳入了 “author” 就按 “author” 查找的情形。若兩者都沒有傳入,就傳回标記為 featured 的 BLOG(這可能是管理者認為,與其傳回大量的無意義随機 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>
           

MyBatis 有一個簡單且适合大多數場景的解決辦法。而在其他場景中,可以對其進行自定義以符合需求。而這,隻需要一處簡單的改動:

<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 元素隻會在子元素傳回任何内容的情況下才插入 “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 關鍵字,并會删掉額外的逗号(這些逗号是在使用條件語句給列指派時引入的)。

來看看與 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 是值。

<select id="selectPostIn" parameterType="map" resultType="domain.blog.Post">
  SELECT * FROM POST P
  WHERE ID in
  <foreach item="id" index="index" collection="ids"
      open="(" separator="," close=")">
        #{id}
  </foreach>
</select>
           

<!-- 
    id:這個是唯一辨別sql代碼片段
    經驗 :    基于單表寫的sql代碼重用性比較高】
        :    就是在sql代碼裡不要出現where
 -->
<sql id="query_list">
    <if test="student!=null and student!=''">
            <if test="student.name!=null and student.name!=''">
                and name=#{student.name}
            </if>
            <if test="student.sex!=null and student.sex!=''">
                and sex=#{student.sex}
            </if> 
        </if>
</sql>
           
<!-- 拼接成功的時候,這個where會自動去掉第一個 and -->
<where>
  <include refid="query_list"></include>    
</where>