在第二篇教程結尾時,我們有簡單的使用動態SQL語句。在這一節,我們將詳細討論它。
1.if標籤
在這裏直接貼出來我們上一節所寫的代碼
<!--
1=1: 永遠爲真,目的是拼接後面的AND語句
在這裏用到了動態SQL語句,根據屬性值是否爲空來動態生成SQL語句
CONCAT:拼接函數,將%#{u.name}%進行拼接
-->
<select id="select" resultType="com.yky.springboot.entities.User">
SELECT * FROM `user` WHERE 1=1
<if test="u.name != null and u.name != ''">
AND `name` LIKE CONCAT('%',#{u.name},'%')
</if>
<if test="u.phone != null and u.phone != ''">
AND `phone` = #{u.phone}
</if>
</select>
在這裏使用了SQL,如果test得到的結果爲真,則標籤體裏的內容得以拼接到前面的sql語句中。
如果u.name 不爲空,且u.phone不爲空,則實際生成的sql語句是這樣的:
SELECT * FROM `user`
WHERE 1=1
AND `name` LIKE CONCAT('%',#{u.name},'%')
AND `phone` = #{u.phone}
如果u.name和u.phone都爲空,則實際生成的sql語句是這樣的:
SELECT * FROM `user` WHERE 1=1
# 等價於
SELECT * FROM `user`
“WHERE 1=1”在這裏沒有什麼實際的意義,目的只是爲了方便拼接if標籤體內的sql語句。
在這裏提出一個容易混淆的點:
#{變量名}取值的方式只適用於標籤體內的SQL‘語句,if標籤的判斷條件是這樣寫的:
test=" u.phone != null and u.phone != ‘’ "
而不是
test=" #{u.phone} != null and #{u.phone} != ‘’ "
2.choose、when、otherwise標籤
有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。
假設現在有一種需求:輸入用戶手機號則有限根據手機號精確查詢,其次再根據名字模糊查詢,否則則根據生日查詢。
先來編寫Mapper接口代碼:
/**
* 輸入用戶手機號則有限根據手機號精確查詢,其次再根據名字模糊查詢,否則則根據生日查詢
* @param u user對象
* @return
*/
List<User> selectByPhoneOrNameOrBirthday(@Param("u") User u);
接下來編寫xml映射文件
<select id="selectByPhoneOrNameOrBirthday" resultType="com.yky.springboot.entities.User">
SELECT * FROM `user` WHERE 1=1
<choose>
<when test="u.phone != null and u.phone != ''">
AND `phone` = #{u.phone}
</when>
<when test="u.name != null and u.name != ''">
AND `name` LIKE CONCAT('%',#{u.name},'%')
</when>
<otherwise>
AND `birthday` = #{u.birthday}
</otherwise>
</choose>
</select>
這種寫法有點類似於
switch(x) {
case xxx:
語句
break;
case xxx:
語句
break;
default:
語句
}
測試
void selectByPhoneOrNameOrBirthday() throws ParseException {
User user = new User();
user.setBirthday(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse("2020-03-16 14:31:21"));
//這一句將查詢出小明
List<User> users = userMapper.selectByPhoneOrNameOrBirthday(user);
System.out.println(users);
//這一句將查詢出小紅
user.setName("紅");
users = userMapper.selectByPhoneOrNameOrBirthday(user);
System.out.println(users);
//這一句將查詢出小王
user.setPhone("16666666666");
users = userMapper.selectByPhoneOrNameOrBirthday(user);
System.out.println(users);
}
輸出結果
3. where、trim、set標籤
前面我們的where語句都是這樣寫的WHERE 1=1 動態拼接的AND語句。如果你覺得WHERE 1=1這種方式不太優雅,那正好這裏有一個替代的方案。
其實,可以直接使用where標籤來完成where語句的動態拼接,且只需要微微的改動即可。if標籤那裏所舉例的映射文件代碼可以改寫成下面這種方式:
<select id="select" resultType="com.yky.springboot.entities.User">
SELECT * FROM `user`
<where>
<if test="u.name != null and u.name != ''">
`name` LIKE CONCAT('%',#{u.name},'%')
</if>
<if test="u.phone != null and u.phone != ''">
AND `phone` = #{u.phone}
</if>
</where>
</select>
where 元素只會在子元素有返回內容的情況下才插入 “WHERE” 子句。而且,若子句的開頭爲 “AND” 或 “OR”,where 元素也會將它們去除。
若你想使用其他語句,而不是WHER語句,則可以使用trim標籤進行定製。比如,和 where 元素等價的自定義 trim 元素爲:
<select id="select" resultType="com.yky.springboot.entities.User">
SELECT * FROM `user`
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="u.name != null and u.name != ''">
`name` LIKE CONCAT('%',#{u.name},'%')
</if>
<if test="u.phone != null and u.phone != ''">
AND `phone` = #{u.phone}
</if>
</trim>
</select>
上述語句的作用:
1.拿到trim標籤體內動態生成的SQL語句,如果語句開頭有prefixOverrides屬性所指定的內容,則自動刪除(AND/OR後面的空格必須)該內容。
2.拼接prefix屬性所指定的內容到最開頭。
用於動態更新語句的類似解決方案叫做 set。set 元素可以用於動態包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateById">
UPDATE `user`
<set>
<if test="u.name != null and u.name != ''">`name` = #{u.name},</if>
<if test="u.phone != null and u.phone != ''">`phone` = #{u.phone},</if>
<if test="u.birthday != null and u.birthday != ''">`birthday` = #{u.birthday}</if>
</set>
WHERE `id` = #{u.id};
</update>
這個例子中,set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)。
如果等價的寫成trim語句的話,則這樣寫
<update id="updateById">
UPDATE `user`
<trim prefix="SET" suffixOverrides=",">
<if test="u.name != null and u.name != ''">`name` = #{u.name},</if>
<if test="u.phone != null and u.phone != ''">`phone` = #{u.phone},</if>
<if test="u.birthday != null and u.birthday != ''">`birthday` = #{u.birthday}</if>
</trim>
WHERE `id`
</update>
suffixOverrides=",":指定在後綴刪除多餘的逗號。
4. foreach
4.1 批量插入
假設需要批量插入集合中的數據,則可以使用foreach標籤進行遍歷集合並動態生成SQL語句。
Mapper接口方法:
/**
* 批量插入users集合中的元素
* @param users 要插入的集合
* @return
*/
boolean batchInsert(@Param("users") List<User> users);
映射文件代碼:
<insert id="batchInsert">
INSERT INTO `user`(`name`, `phone`, `birthday`)
VALUES
<foreach collection="users" item="user" index="index" separator=",">
(#{user.name},#{user.phone},#{user.birthday})
</foreach>
</insert>
foreach 元素的功能非常強大,它允許你指定一個集合,聲明可以在元素體內使用的集合項(item)和索引(index)變量。它也允許你指定開頭與結尾的字符串以及集合項迭代之間的分隔符。這個元素也不會錯誤地添加多餘的分隔符,看它多智能!
提示: 你可以將任何可迭代對象(如 List、Set 等)、Map 對象或者數組對象作爲集合參數傳遞給
foreach。當使用可迭代對象或者數組時,index 是當前迭代的序號,item 的值是本次迭代獲取到的元素。當使用 Map 對象(或者 Map.Entry 對象的集合)時,index 是鍵,item 是值。
測試:
@Test
void batchInsert() {
User user1 = new User();
user1.setName("test1");
user1.setPhone("1111111111");
user1.setBirthday(new Date());
User user2 = new User();
user2.setName("test2");
user2.setPhone("2222222222");
user2.setBirthday(new Date());
User user3 = new User();
user3.setName("test3");
user3.setPhone("3333333333");
user3.setBirthday(new Date());
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
users.add(user3);
userMapper.batchInsert(users);
}
運行結果
4.2 查找集合中存在的記錄
假設需要查找集合中存在的元素,我們通常使用IN關鍵字:
SELECT * FROM `user` WHERE `id` IN (1,3,5)
IN 後面所跟集合中的元素是可變的,這就需要動態生成SLQ語句了。
Mapper接口代碼:
/**
* 查找出id集合中的所有記錄
* @param ids 存放id的集合
* @return
*/
List<User> selectInList(@Param("ids") List<Long> ids);
xml映射文件代碼:
<select id="selectInList" resultType="com.yky.springboot.entities.User">
SELECT * FROM `user`
WHERE `id` IN
<foreach collection="ids" item="id" index="index" separator="," open="(" close=")">
#{id}
</foreach>
</select>
簡單解釋:
ids:遍歷的集合
id:本次迭代獲取的值
index:當前迭代序號
,:元素分割符
(:字符串開頭
):字符串結尾
測試代碼:
@Test
void selectInList() {
Long[] ids = {1L,3L,5L,7L};
List<User> users = userMapper.selectInList(Arrays.asList(ids));
System.out.println(users);
}
5.Debug技巧
在開發中,難免會遇到SQL語句寫錯的情況,在控制檯打印當前運行的SQL語句是一種比較好的方式。
在application.yml中開啓控制檯打印sql語句:
logging:
level:
com.yky.springboot.mapper: debug
只需指定Mapper接口所在包的日誌級別爲debug即可。