MyBatis標籤實現的動態SQL語句

AngeliaZheng發表於2018-09-11

MyBatis 的強大特性之一便是它的動態 SQL。如果你有使用 JDBC 或其他類似框架的經驗,你就能體會到根據不同條件拼接 SQL 語句有多麼痛苦。利用動態 SQL 這一特性可以徹底擺脫這種痛苦。

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

MyBatis 的動態sql語句是基於OGNL表示式的。可以方便的在 sql 語句中實現某些邏輯。總體說來 MyBatis 動態 SQL 語句主要有以下幾類:

  • if 語句 (簡單條件判斷)
  • where (用來簡化sql語句中where條件判斷的,能智慧的處理 and or,不必擔心多餘導致語法錯誤)
  • set (主要用於更新時)
  • trim (對包含的內容加上 prefix,或者 suffix 等,字首,字尾)
  • choose (when,otherwize) (相當於java 語言中的 switch ,與 jstl 中的choose 很類似)
  • foreach (實現 mybatis in 語句查詢時特別有用)

1. if 語句

動態 SQL 通常要做的事情是有條件地包含 where 子句的一部分。

首先看一個很普通的查詢:

<!-- 查詢作者列表,like作者名字 -->
<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
	SELECT * FROM author a 
	WHERE a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
            AND a.mobile = #{mobile}
</select>

但是當 authorName 或 mobile 為 null 時,此語句很可能報錯或查詢結果為空。此時我們使用 if 動態 sql 語句先進行判斷,如果值為 null 或等於空字串,我們就不進行此條件的判斷,增加靈活性。

<!-- 新增if(判斷引數) - 將實體類 Author 不為空的屬性作為 where 條件 -->
<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
	SELECT * FROM author a
	WHERE
	<if test="authorName != null">
		a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
	</if>
	<if test="mobile != null">
		AND a.mobile = #{mobile, jdbcType=VARCHAR}
	</if>
</select>

這條語句提供了一個可選的文字查詢型別的功能。

2. where 語句

上面的if語句,如果所有條件都沒有匹配上將會怎樣?最終這條 SQL 會變成這樣:

SELECT * FROM author a
    WHERE

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

SELECT * FROM author a
    WHERE
        AND a.mobile = #{mobile, jdbcType=VARCHAR}

這個查詢也會失敗。這個問題可以用一些方法解決,比如加一個永遠為true的條件。

<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
	SELECT * FROM author a
	WHERE 1=1
	<if test="authorName != null">
		AND a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
	</if>
	<if test="mobile != null">
		AND a.mobile = #{mobile, jdbcType=VARCHAR}
	</if>
</select>

MyBatis 有一個簡單的處理就能得到想要的效果:

<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
	SELECT * FROM author a
	<where>
		<if test="authorName != null">
			a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
		</if>
		<if test="mobile != null">
			AND a.mobile = #{mobile, jdbcType=VARCHAR}
		</if>
	</where>
</select>

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

3. set 更新語句

當 update 語句中沒有使用 if 標籤時,如果有一個引數為 null,都會導致錯誤。

當在 update 語句中使用 if 標籤時,如果前面的 if 沒有執行,則或導致逗號多餘錯誤。使用set標籤可以將動態的配置 SET 關鍵字,並剔除追加到條件末尾的任何不相關的逗號。類似的用於動態更新語句的解決方案叫做 set。set 元素可以被用於動態包含需要更新的列,而捨去其他的。使用 if+set 標籤修改後,如果某項為 null 則不進行更新,而是保持資料庫原值。如下示例:

<!-- if/set(判斷引數) - 將實體 Author 類不為空的屬性更新 -->
<update id="updateAuthor" parameterType="Author">
	UPDATE author
	<set>
	    <if test="authorName != null and authorName != '' "> author_name = #{authorName}, </if>
	    <if test="mobile != null and mobile != '' "> mobile = #{mobile}, </if>
	</set>
	WHERE id = #{id};
</update>

set 標籤元素主要是用在更新操作的時候,它的主要功能和 where 標籤元素其實是差不多的,主要是在包含的語句前輸出一個 set,然後如果包含的語句是以逗號結束的話將會把該逗號忽略,如果 set 包含的內容為空的話則會出錯。有了 set 元素就可以動態的更新那些修改了的欄位。

4. trim 語句

如果 where 或 set 元素沒有按正常套路出牌,我們還是可以通過自定義 trim 元素來定製我們想要的功能。比如,和 where 元素等價的自定義 trim 元素為:

<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>

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

prefixOverrides 屬性會忽略通過管道分隔的文字序列(注意此例中的空格也是必要的)。它帶來的結果就是所有在 prefixOverrides 屬性中指定的內容將被移除,並且插入 prefix 屬性中指定的內容。

suffixOverrides屬性會忽略字尾中的值。即suffixOverrides屬性中指定的內容會被移除,並插入prefix 屬性中指定的內容。

trim 是更靈活用來去處多餘關鍵字的標籤,它可以用來實現 where 和 set 的效果。

<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
	SELECT * FROM author a
	<trim prefix="WHERE" prefixOverrides="AND|OR">
		<if test="authorName != null">
			a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
		</if>
		<if test="mobile != null">
			AND a.mobile = #{mobile, jdbcType=VARCHAR}
		</if>
	</trim>
</select>

<!-- if/set(判斷引數) - 將實體 Author 類不為空的屬性更新 -->
<update id="updateAuthor" parameterType="Author">
	UPDATE author
	<set>
		<if test="authorName != null and authorName != '' "> author_name = #{authorName}, </if>
		<if test="mobile != null and mobile != '' "> mobile = #{mobile}, </if>
	</set>
	WHERE id = #{id};
</update>

5. choose,when,otherwise語句

有時候我們並不想應用所有的條件,而只是想從多個選項中選擇一二個。而使用if標籤時,只要test中的表示式為 true,就會執行 if 標籤中的條件。MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。if 標籤是與(and)的關係,而 choose 是或(or)的關係。

choose 標籤是按順序判斷其內部 when 標籤中的 test 條件出否成立,如果有一個成立,則 choose 結束。當 choose 中所有 when 的條件都不滿則時,則執行 otherwise 中的 sql。類似於Java 的 switch 語句,choose 等同於 switch,when 等同於 case,otherwise 則等同於 default。

如下面的例子。choose 會從上到下選擇一個 when 標籤的 test 為 true 的 sql 執行。安全考慮,我們使用 where 將 choose 包起來。

<!-- choose(判斷引數) - 按順序將實體類 Author 第一個不為空的屬性作為:where條件 -->
<select id="getAuthorList" resultMap="resultAuthorMap" parameterType="Author">
	SELECT * FROM author a
	<where>
		<choose>
			<when test="authorName != null">
				a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
			</when>
			<when test="mobile != null">
				AND a.mobile = #{mobile, jdbcType=VARCHAR}
			</when>
			<otherwise>
				a.mobile = "13666666666"
			</otherwise>
		</choose>
	</where>
</select>

when元素表示當 when 中的條件滿足的時候就輸出其中的內容,跟 JAVA 中的 switch 效果差不多的是按照條件的順序,當 when 中有條件滿足的時候,就會跳出 choose,即所有的 when 和 otherwise 條件中,只有一個會輸出,當所有的條件都不滿足的時候就輸出 otherwise 中的內容。

6. foreach

動態 SQL 的一個常用的必要操作是需要對一個集合進行遍歷,通常是在構建 IN 條件語句的時候。比如:

<select id="getAuthorListByIds" parameterType="java.util.List" resultMap="resultAuthorMap" >
	SELECT * FROM author
	WHERE id in
	<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
               #{item}       
        </foreach>
</select>
	
<select id="getAuthorListByIds" parameterType="java.util.ArrayList" resultMap="resultAuthorMap">
	SELECT * FROM author
        WHERE id in
	<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
		#{item}
	</foreach>
</select>

<select id="dynamicForeach3Test" parameterType="java.util.HashMap" resultMap="resultAuthorMap">
	SELECT * FROM author
	WHERE id in
	<foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
		#{item}
	</foreach>
</select>

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

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

相關文章