目錄
1. 前言
2.?動態插入
2.1 if 標簽?
2.2 trim 標簽
2.2.1?注解完成動態 SQL
3. 動態查詢
3.1 添加 1 = 1
3.2 where 標簽
?4. 動態更新
4.1 set 標簽
?5. foreach 標簽
6. sql 標簽 & include 標簽
1. 前言
之前博文所講的 MyBatis SQL 操作, 都必須按照注解或者標簽中定義的 SQL 嚴格執行, 一個注解/標簽, 對應的是不同的 SQL 操作. 比如: 一個 select 注解中, 定義了?3 個字段, 那這個注解只能插入這三個列的值, 如果想要插入更多的列, 必須重新再寫一個注解.
而動態 SQL, 我們可以根據用戶輸入的值, 動態的進行字段的插入, 當用戶輸入這一字段的值時, 就插入這一字段. 當用戶沒有輸入這一字段的值時, 就不插入這一字段. 能夠完成不同條件下, 不同的 SQL 拼接.
注意: 本文主要通過 XML 完成動態 SQL!!?
2.?動態插入
2.1 if 標簽?
通過 if 標簽, 對傳入的參數的值進行判斷, 若未傳入相關列的值, 則不拼接該列的 SQL 片段.?
如下圖所示, 只有當傳入參數的值不為 null 時, 才會拼接該 SQL 片段.
注意: if 標簽, 判斷的是傳入的參數的值(#{} 中的參數), 即 java 屬性, 而不是數據庫字段!!
因此, 我們就可以通過這一個 insert 標簽對應的方法, 進行任意字段的插入:
但是, 當對最后一個字段傳入的值為 null 時, 錯誤就發生了:
這是因為最后一個字段?gender?的值為 null, 導致 if 標簽的判斷為 false, 因此 gender 字段的 SQL 沒有進行拼接, 導致倒數第二個字段 phone 后多了一個逗號(,), 拼接的內容是 "phone , "因此造成語法錯誤.
此時, 就需要 trim 標簽出馬了~~
2.2 trim 標簽
trim 標簽中有以下屬性:
- prefix: 表示整個語句塊, 以 prefix 的值作為前綴
- suffix: 表示整個語句塊, 以suffix的值作為后綴
- prefixOverrides: 表示整個語句塊要去除掉的前綴
- suffixOverrides: 表示整個語句塊要去除掉的后綴
有 trim 標簽, 就可以通過 prefixOverrides/suffixOverrides 屬性解決上文中由于沒有拼接最后一個字段, 導致倒數第二個字段后拼接了逗號(,)的問題, 并且還可以通過 prefix/suffix 屬性將 SQL 中的 "(" 和 ")" 自動拼接到 SQL 中:
?運行觀察結果:
<insert id="insertUser4">insert into user_info<trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=","><if test="username != null">username,</if><if test="password != null">password,</if><if test="age != null">age,</if><if test="phone != null">phone,</if><if test="gender != null">gender</if></trim>values<trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=","><if test="username != null">#{username},</if><if test="password != null">#{password},</if><if test="age != null">#{age},</if><if test="phone != null">#{phone},</if><if test="gender != null">#{gender}</if></trim></insert>
2.2.1?注解操作動態 SQL
上文是通過?XML 進行的動態 SQL?, 在注解中同樣也是可以的, 并且方式相同.
若要通過注解完成動態 SQL, 只需將上文?XML 中動態 SQL 的代碼, 以字符串的形式拼接到 <script> 標簽中即可.
@Insert("<script>" +"insert into user_info " +"<trim prefix=\"(\" suffix=\")\" prefixOverrides=\",\" suffixOverrides=\",\">" +"<if test=\"username != null\">username,</if>" +"<if test=\"password != null\">password,</if>" +"<if test=\"age != null\">age,</if>" +"<if test=\"phone != null\">phone,</if>" +"<if test=\"gender != null\">gender</if>" +"</trim> values " +"<trim prefix=\"(\" suffix=\")\" prefixOverrides=\",\" suffixOverrides=\",\">" +"<if test=\"username != null\">#{username},</if>" +"<if test=\"password != null\">#{password},</if>" +"<if test=\"age != null\">#{age},</if>" +"<if test=\"phone != null\">#{phone},</if>" +"<if test=\"gender != null\">#{gender}</if>" +"</trim>" +"</script>")int insertUser4(UserInfo userInfo);
3. 動態查詢
進行 select 查詢操作時, 常常會用到 where 關鍵字進行條件查詢, 進行條件查詢時, 同樣也可以通過 if 標簽進行動態查詢:
但是, 當參數 phone 為 null 時, 就發生錯誤了:
出錯的原因很簡單: 因為?phone 屬性的值為 null, 導致 where 關鍵字后跟的是 "and gender", 導致 SQL 語法錯誤.
解決辦法很簡單, 使用 trim 標簽, 將 and 關鍵字去除即可:
但是, 當 phone 和 gender 兩個屬性都為 null 時, 問題又出現了:
這里錯誤的原因是: phone 和 gender 都為 null, 導致出現了多余的 where 關鍵字, 導致 SQL 語法錯誤.
解決方法有兩種:
- 添加 1 = 1, 保證?where 后有條件
- 使用 <where>, 去除 where 關鍵字
3.1 添加 1 = 1
在 where 關鍵字后添加 1 = 1, 保證 where 后有條件. 并在每條拼接的 SQL 片段前添加 and, 保證 SQL 片段可以進行拼接:?
<select id="selectByCondition" resultType="com.study.mybatis.model.UserInfo">select * from user_infowhere 1 = 1<if test="phone != null">and phone = #{phone}</if><if test="gender != null">and gender = #{gender}</if></select>
3.2 where 標簽
使用 where 標簽代替 where 關鍵字:
where 標簽有兩個作用:
- 當 where 標簽語塊內沒有內容時, 不會生成 where 關鍵字
- 當 where 標簽語塊內有查詢條件時, 會自動生成 where 關鍵字, 并且會去除最前面的 and 關鍵字
因此, 即使查詢條件全為 null, 或者第一個查詢條件前有 and 關鍵字, where 標簽都會幫我們解決這些問題:
但是, 在實際工作中, 要避免使用 where 標簽, 因為當我們遺漏傳入條件查詢的參數時, 程序也會正確執行, 而無任何條件限制的 select 是非常嚴重的!!
<select id="selectByCondition" resultType="com.study.mybatis.model.UserInfo">select * from user_info<where><if test="phone != null">phone = #{phone}</if><if test="gender != null">and gender = #{gender}</if></where>
</select>
?4. 動態更新
update 語句也可以結合 if 和 trim 標簽, 完成動態更新操作:
<update id="updateUser2">update user_infoset<trim suffixOverrides=","><if test="username != null">username = #{username},</if><if test="password != null">password = #{password},</if><if test="age != null">age = #{age}</if></trim>where id = #{id}</update>
4.1 set 標簽
除了上文將?if 和 trim 標簽結合完成動態的 update 操作外, 也可以使用 set 標簽來完成.
set 標簽的作用:
- 生成 set 關鍵字
- 去除多余的逗號(只能去除最后一個逗號)
<update id="updateUser3">update user_info<set><if test="username != null">username = #{username},</if><if test="password != null">password = #{password},</if><if test="age != null">age = #{age}</if></set>where id = #{id}</update>
?5. foreach 標簽
在進行數據庫操作時, 我們常會在一個條 SQL 語句中, 對數據批量的進行操作, 如下:
SELECT * FROM user_info where id = 1 or id = 2 or id = 3;INSERT into user_info (username, `password`) VALUES ('1', '1'), ('2', '2');UPDATE user_info set delete_flag = 1 WHERE id IN(1, 2, 3);DELETE from user_info WHERE id IN(4, 5, 6);
如果要通過 MyBatis 對數據進行批量處理, 就需要使用 foreach 標簽. \
使用 foreach 批量操作數據, 傳入的參數必定是一個集合(包括 Map).
foreach 標簽中有以下幾個關鍵屬性:
-
collection?(必須):?指定要迭代的集合. (如果方法參數是
List
,collection
就是List
的參數名) -
item?(必須):?指定迭代過程中元素的名稱.
-
index?(可選):?表示集合中每個元素的索引
-
separator?(可選):?指定每個元素之間的分隔符
-
open?(可選):?循環開始前要添加的字符串. 通常用于在?IN?語句中添加左括號?(
-
close?(可選):?循環結束后要添加的字符串. 通常用于在?IN?語句中添加右括號?)
<!-- DELETE from user_info WHERE id IN(1, 2, 3);--><delete id="deleteUser2">delete from user_info whereid in<foreach collection="ids" open="(" close=")" item="id" separator=",">#{id}</foreach></delete>
<!-- delete FROM user_info where id = 1 or id = 2 or id = 3;--><delete id="deleteUser3">delete from user_info where<foreach collection="ids" item="id" separator="or">id = #{id}</foreach></delete>
<!-- insert into user_info (username, `password`) VALUES ('1', '1'), ('2', '2');--><insert id="insertUser1">insert into user_info (username, password) values<foreach collection="userInfos" item="user" separator=",">(#{user.username}, #{user.password})</foreach></insert>
6. sql 標簽 & include 標簽
sql 標簽和 include 標簽通常搭配使用.
在 sql 標簽中定義一個 sql 片段, 并指定這個 sql 標簽的 id.
在其他任何標簽中, 就可以通過 include 標簽指定某個 sql 標簽的 id, 直接引用該 sql 標簽中的 sql 片段.
也就是說, 我們可以直接通過 sql 標簽的 id, 實現 sql 片段的復用.
注意:
SQL 標簽中可以定義任意的 SQL 片段(字符串), 不一定是可執行的 SQL !!
END?