MyBatis—動態 SQL
一、動態 SQL 的核心作用
動態 SQL 主要解決以下問題:
-
靈活性:根據不同的輸入參數生成不同的 SQL 語句(如條件查詢、批量操作)。
-
可維護性:減少重復代碼,通過標簽化邏輯提高 SQL 可讀性。
-
安全性:自動處理參數綁定,防止 SQL 注入。
二、常用動態 SQL 標簽
MyBatis 提供了以下標簽來實現動態 SQL:
1. <if>
:條件判斷
- 根據參數值是否滿足條件,決定是否包含 SQL 片段。
- 示例:
<select id="findUser" resultType="User">SELECT * FROM users<where><if test="name != null and name != ''">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where>
</select>
- 說明:如果
name
或age
為空,則對應的條件不會被包含。
2. <choose>/<when>/<otherwise>
:多條件選擇
-
類似于 Java 的
switch-case
,按順序判斷條件。 -
示例:
<select id="findUserByCondition" resultType="User">SELECT * FROM users<where><choose><when test="name != null">AND name = #{name}</when><when test="email != null">AND email = #{email}</when><otherwise>AND status = 'active'</otherwise></choose></where> </select>
-
說明:按順序判斷
name
、email
,若都不滿足則執行默認條件。
3. <where>
:智能處理 WHERE 子句
-
自動去除多余的
AND
或OR
,并自動添加WHERE
關鍵字。 -
示例:
<select id="findUser" resultType="User">SELECT * FROM users<where><if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where> </select>
- 結果:若
name
和age
都為空,則生成SELECT * FROM users
(無WHERE
子句);若name
不為空,則生成WHERE name = ?
。
- 結果:若
4. <set>
:動態更新字段
-
用于
UPDATE
語句,自動去除末尾逗號。 -
示例:
<update id="updateUser">UPDATE users<set><if test="name != null">name = #{name},</if><if test="email != null">email = #{email},</if></set>WHERE id = #{id} </update>
- 結果:若
name
和email
都不為空,生成SET name = ?, email = ?
;若只有一個字段不為空,自動去除末尾逗號。
- 結果:若
5. <foreach>
:遍歷集合
-
用于批量操作(如
IN
子句、批量插入)。 -
示例:
<select id="findUsersByIds" resultType="User">SELECT * FROM usersWHERE id IN<foreach item="id" collection="list" open="(" separator="," close=")">#{id}</foreach> </select>
- 結果:若傳入的
list
為[1, 2, 3]
,生成WHERE id IN (1, 2, 3)
。
- 結果:若傳入的
6. <trim>
:靈活拼接 SQL
-
手動控制 SQL 片段的前綴和后綴,常用于復雜邏輯。
-
示例:
<trim prefix="WHERE" prefixOverrides="AND |OR "><if test="name != null">AND name = #{name}</if> </trim>
- 說明:若
name
為空,則不生成WHERE
;若name
不為空,生成WHERE name = ?
。
- 說明:若
三、動態 SQL 的實現原理
- XML 解析:MyBatis 啟動時加載 Mapper XML 文件,解析動態 SQL 標簽。
- SQL 拼接:運行時根據傳入參數動態生成 SQL 片段。
- 參數綁定:使用
#{}
綁定參數,防止 SQL 注入。 - 預編譯:最終生成的 SQL 被發送給數據庫驅動,創建
PreparedStatement
。
四、動態 SQL 的應用場景
- 條件查詢:根據用戶輸入動態過濾條件。
- 批量操作:批量插入、更新、刪除。
- 多表關聯:根據業務需求動態關聯不同表。
- 復雜業務邏輯:如動態排序、分頁等。
五、最佳實踐
- 避免復雜嵌套:過多嵌套會降低可讀性,建議拆分邏輯。
- 合理使用
<where>
和<set>
:簡化 SQL 片段。 - 測試動態 SQL:通過日志查看生成的 SQL,確保邏輯正確。
- 參數校驗:在業務層校驗參數,避免無效條件。
六、示例:綜合使用動態 SQL(實戰)
比如我們已經寫完了controller層,entity層,mapper層,service層等Impl。
動態SQL實現:修改mapper層:(注釋掉SQL注解)
@Mapper
public interface UsersMapper {@Select("insert into users(account,password,uname,gender,age,phone,email,address,avatar,regtime,uflag) values(#{account},#{password},#{uname},#{gender},#{age},#{phone},#{email},#{address},#{avatar},#{regtime},#{uflag})")void addUser(Users users);@Select("select * from users where account=#{account}")Users getUserByAccount(String account);// @Select("select * from users where uflag = #{uflag} order by regtime desc")List<Users> queryUsersByUflag(Users users);//根據id查詢用戶@Select("select * from users where account = #{id}")Users queryUsersById(String id);//審核通過
// @Update("update users set uflag = #{uflag} where account = #{account}")void updateUser(Users users);//刪除家長@Delete("delete from users where account = #{id}")void delUsers(String id);
}
這些SQL注解我們寫進resources/mapper/UsersMapper.xml這里。
例如:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bkty.turtorsystem.mapper.UsersMapper"><!--根據動態的條件查詢用戶--><select id="queryUsersByUflag" resultType="Users"parameterType="Users">select * from users<where><if test="uflag != null and uflag != ''">uflag = #{uflag}</if><if test="account != null and account != ''">and account = #{account}</if><if test="uname != null and uname != ''">and uname = #{uname}</if><if test="password != null and password != ''">and password = #{password}</if><if test="email != null and email != ''">email = #{email}</if><if test="phone != null and phone != ''">and phone like concat('%',#{phone},'%')</if><if test="gender != null and gender != ''">and gender = #{gender}</if><if test="age != null and age != ''">and age = #{age}</if><if test="address != null and address != ''">address = #{address}</if><if test="condition != null and condition != ''">${condition}</if></where>order by regtime desc</select><update id="updateUser" parameterType="Users">update users<set><if test="account != null and account != ''">account = #{account},</if><if test="password != null and password != ''">password = #{password},</if><if test="uname != null and uname != ''">uname = #{uname},</if><if test="gender != null and gender != ''">gender = #{gender},</if><if test="age != null and age != ''">age = #{age},</if><if test="phone != null and phone != ''">phone = #{phone},</if><if test="email != null and email != ''">email = #{email},</if><if test="address != null and address != ''">address = #{address},</if><if test="avatar != null and avatar != ''">avatar = #{avatar},</if><if test="regtime != null and regtime != ''">regtime = #{regtime},</if><if test="uflag != null and uflag != ''">uflag =#{uflag},</if></set>where account = #{account}</update>
</mapper>
<mapper namespace="com.bkty.turtorsystem.mapper.UsersMapper">
這個namespace=指向我的mapper,相當于注解SQL,把注解改成了xml。
1. 查詢語句:queryUsersByUflag
<select id="queryUsersByUflag" resultType="Users" parameterType="Users">select * from users<where><if test="uflag != null and uflag != ''">uflag = #{uflag}</if><if test="account != null and account != ''">and account = #{account}</if><!-- 其他字段的 <if> 條件 --><if test="condition != null and condition != ''">${condition}</if></where>order by regtime desc
</select>
功能說明
- 作用:根據傳入的
Users
對象中的字段動態生成 SQL 查詢條件,查詢users
表中的記錄。 - 動態條件:
- 使用
<where>
標簽包裹所有條件,MyBatis 會自動處理AND
或OR
的冗余問題(例如,如果第一個條件不成立,WHERE
關鍵字不會被輸出)。 - 每個
<if>
標簽檢查字段是否非空,若非空則添加對應的查詢條件。 - 特殊字段
condition
使用${condition}
直接拼接 SQL(需注意 SQL 注入風險)。
- 使用
關鍵點
- 字段條件:
- 所有字段(如
uflag
,account
,uname
等)都通過<if>
動態判斷是否添加到查詢條件中。 - 注意:第一個條件(
uflag
)沒有加AND
,但<where>
標簽會自動處理這種情況,避免語法錯誤。
- 所有字段(如
condition
字段:- 使用
${condition}
直接拼接原始 SQL 片段(例如1=1
或status='active'
)。 - 風險:
${}
不會進行參數綁定,存在 SQL 注入風險,需確保傳入值的安全性。
- 使用
- 排序:
- 固定按
regtime
降序排列。
- 固定按
示例
假設傳入的 Users
對象包含 uflag="1"
和 account="test123"
,生成的 SQL 為:
SELECT * FROM users
WHERE uflag = '1' AND account = 'test123'
ORDER BY regtime DESC;
2. 更新語句:updateUser
<update id="updateUser" parameterType="Users">update users<set><if test="account != null and account != ''">account = #{account},</if><!-- 其他字段的 <if> 條件 --><if test="uflag != null and uflag != ''">uflag =#{uflag},</if></set>where account = #{account}
</update>
功能說明
- 作用:根據傳入的
Users
對象中的字段動態更新users
表中的記錄。 - 動態更新字段:
- 使用
<set>
標簽包裹所有字段更新邏輯,MyBatis 會自動去除末尾多余的逗號。 - 每個
<if>
標簽判斷字段是否非空,若非空則更新對應字段。
- 使用
- 更新條件:
- 使用
account = #{account}
作為更新條件(需確保account
是唯一標識字段)。
- 使用
關鍵點
- 字段更新:
- 所有字段(如
account
,password
,uname
等)都通過<if>
動態判斷是否更新。 - 注意:每個字段條件后都有逗號
,
,但<set>
會自動去除最后一個字段的逗號。
- 所有字段(如
- 更新條件:
- 使用
account = #{account}
作為更新條件,需確保account
是唯一值(否則可能更新多條記錄)。
- 使用
- 潛在問題:
- 如果
account
不唯一,可能會導致意外更新多條記錄。 - 更推薦使用主鍵(如
id
)作為更新條件。
- 如果
示例
假設傳入的 Users
對象包含 account="test123"
和 uname="NewName"
,生成的 SQL 為:
UPDATE users
SET account = 'test123', uname = 'NewName'
WHERE account = 'test123';
七、總結
MyBatis 的動態 SQL 通過標簽化邏輯,解決了傳統 SQL 硬編碼的問題,使代碼更簡潔、安全且靈活。合理使用 <if>
、<where>
、<foreach>
等標簽,可以大幅提升開發效率和代碼質量。