MyBatis教程[3]----動態SQL

第二篇教程結尾時,我們有簡單的使用動態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即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章