1. 程式人生 > >MyBatis標籤實現的動態SQL語句

MyBatis標籤實現的動態SQL語句

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”。