一、應用場景
MyBatis 的 動態 SQL 是指根據不同的條件動態拼接生成 SQL 語句的能力。它的最大優勢是:避免寫多個 XML 映射語句、避免 SQL 冗余、提升代碼復用性和可維護性。
示例1:用戶可以通過勾選框,勾選不同的數據進行批量刪除,此時:
delete from xxx_table where id in (a, b, c, d, ......)
in后面括號中的數據就是動態的。
示例2:用戶搜索功能,查詢條件不確定(常用于搜索/篩選)
二、使用方式(XML + 標簽)
MyBatis 動態 SQL 主要通過 XML 配置文件中的標簽 來實現,核心標簽如下:
標簽 | 作用 |
---|---|
<if> | 條件判斷 |
<choose> / <when> / <otherwise> | 類似 Java 中的 if-else if-else |
<where> | 自動加上 WHERE 并避免多余的 AND/OR |
<set> | 用于 UPDATE ,自動去除最后的逗號 |
<foreach> | 用于遍歷集合(常見于 IN、批量插入) |
<trim> | 自定義前綴、后綴、去掉前后符號等 |
2-1、<if>標簽
示例:
List<Car> selectByMutiConditions(@Param("brand") String brand,@Param("guidePrice") Double guidePrice,@Param("carType") String carType);
?
<select id="selectByMutiConditions" resultType="car">select * from t_car where 1=1/*1. if標簽中,test屬性是必須的,值:表達式(false/true)2. test屬性可以使用的是:使用了@Param標簽,就用@Param標簽指定的參數名;沒有使用@Param標簽,就用:param1, param2, param3, arg0, arg1, arg2...當使用了pojo,就用pojo中的屬性名3. 在mybatis的動態sql中,不能使用&&,只能使用and*/<if test="brand != null and brand != ''">and brand like "%"#{brand}"%"</if><if test="guidePrice != null and guidePrice != ''">and guidePrice > #{guidePrice}</if><if test="carType != null and carType != ''">and carType = #{carType}</if></select>
【注意點】:
1. if標簽中,test屬性是必須的,值:表達式(false/true)
2. test屬性可以使用的是:
- ??? 使用了@Param標簽,就用@Param標簽指定的參數名;
- ??? 沒有使用@Param標簽,就用:param1, param2, param3, arg0, arg1, arg2...
- ??? 當使用了pojo,就用pojo中的屬性名
3. 在mybatis的動態sql中,不能使用&&,只能使用and。
4、為了防止有部分條件不成立的時候,where后面直接拼接了and,需要加上1=1
2-2、<where>標簽
它的作用是在生成 SQL 語句時:
自動去掉首個條件前多余的
AND
或OR
當有條件時自動加上
WHERE
關鍵字當沒有任何條件時不會生成
WHERE
子句
【注意】:
????????<where>
標簽只對內容中出現的 第一個 AND/OR 進行清理,建議把每個<if>
條件前都加上AND
,它會自動清理第一個多余的。
示例:
?
2-3、<trim>標簽
在 MyBatis 中,<trim>
是一個功能非常強大的 動態 SQL 標簽,它可以靈活地控制:
前綴(prefix):開頭加什么
后綴(suffix):結尾加什么
前綴去除(prefixOverrides):去掉開頭多余的關鍵字,比如
AND
/OR
/,
后綴去除(suffixOverrides):去掉結尾多余的內容,比如
,
你可以把
<trim>
看成<where>
和<set>
的“底層基礎版本”,功能更靈活。
1、常見屬性說明
屬性名 | 說明 |
---|---|
prefix | 給內容加上前綴,比如 "WHERE" 、"SET" |
suffix | 給內容加上后綴 |
prefixOverrides | 去除內容前面多余的前綴(如多余的 "AND" 、"OR" ) |
suffixOverrides | 去除內容最后多余的后綴(如多余的 "," ) |
【注意】:
它們 都是在整體 trim 內容拼接完畢后,一次性處理整個 SQL 片段的前綴和后綴!?
示例 1:動態 WHERE 條件,代替 <where>
<select id="selectUser" parameterType="User" resultType="User">SELECT * FROM user<trim prefix="WHERE" prefixOverrides="AND |OR "><if test="username != null"> AND username = #{username} </if><if test="gender != null"> AND gender = #{gender} </if></trim>
</select>
效果(如果 username != null 且 gender == null):
SELECT * FROM user WHERE username = 'Tom'
自動去掉多余的 AND
,和 <where>
效果一樣,但更靈活(你也可以換成 OR)。
示例 2:動態更新字段,代替 <set>
<update id="updateUser" parameterType="User">UPDATE user<trim prefix="SET" suffixOverrides=","><if test="username != null"> username = #{username}, </if><if test="gender != null"> gender = #{gender}, </if></trim>WHERE id = #{id}
</update>
效果:
如果 username = 'Tom', gender = null:
UPDATE user SET username = 'Tom' WHERE id = 1
自動去掉最后多余的逗號
,
示例 3:完全自定義的 SQL 拼接
<trim prefix="(" suffix=")" suffixOverrides=","><foreach collection="ids" item="id">#{id},</foreach>
</trim>
輸出示例:
(1, 2, 3)
2-4、<set>標簽
<set>
標簽的作用是:
自動添加
SET
關鍵字自動去除多余的結尾逗號
,
與
<if>
標簽組合使用,實現根據條件動態更新字段
目的:希望更新的時候,只更新不為null的字段,其余字段不動!
?
2、基本語法結構
<update id="updateUser" parameterType="User">UPDATE user<set><if test="username != null"> username = #{username}, </if><if test="email != null"> email = #{email}, </if><if test="age != null"> age = #{age}, </if></set>WHERE id = #{id}
</update>
3、執行邏輯過程
假設只傳了 username
和 age
,email
是 null。
MyBatis 會生成這樣的 SQL:
UPDATE user
SET username = ?, age = ?
WHERE id = ?
注意:
? 自動添加了
SET
? 自動移除了末尾多余的
,
4、和 <trim>
的關系
<set>
本質上就是 <trim>
的封裝版本:
等價于:
<trim prefix="SET" suffixOverrides=",">...
</trim>
也就是說,如果你希望自定義更多行為(如不使用 SET
、用別的關鍵字),可以改用 <trim>
標簽。
2-5、<choose>、<when>、<otherwise>
MyBatis 中的 <choose>
標簽是用來實現 類似 Java 中 if-else if-else 結構的動態 SQL 分支選擇語句,非常適合你有多個條件分支,但只想執行其中一個的情況。
1、 作用:
<choose>
是 MyBatis 提供的一個邏輯分支標簽,用來表示:
如果滿足第一個條件就執行它,否則判斷第二個條件……都不滿足則執行默認分支。
它的結構和 Java 中的 if ... else if ... else
是一致的:
if (a != null) {// ...
} else if (b != null) {// ...
} else {// default
}
在 MyBatis 中寫法如下:
<choose><when test="a != null">...</when><when test="b != null">...</when><otherwise>...</otherwise>
</choose>
2、使用示例
例子:根據傳入的條件查詢用戶
<select id="findUser" parameterType="map" resultType="User">SELECT * FROM user<where><choose><when test="id != null">id = #{id}</when><when test="username != null">username = #{username}</when><otherwise>gender = 'male'</otherwise></choose></where>
</select>
3、執行邏輯:
假設傳入參數是:
Map<String, Object> param = new HashMap<>();
param.put("username", "Tom");
生成的 SQL 就是:
SELECT * FROM user WHERE username = 'Tom'
如果傳入參數是:
param.put("id", 123);
param.put("username", "Tom");
優先滿足第一個條件:
SELECT * FROM user WHERE id = 123
如果兩個都沒傳,則使用 otherwise
:
SELECT * FROM user WHERE gender = 'male'
4、幾點注意事項
特性 | 說明 |
---|---|
<choose> 內只能選中 一個 <when> 被執行 | 只執行第一個匹配成功的 <when> |
<otherwise> 是可選的 | 不寫也可以 |
多個 <when> 的順序很重要 | 從上往下匹配,匹配到就停 |
2-6、<foreach>標簽
MyBatis 中的 <foreach>
標簽是動態 SQL 中非常重要的一個標簽,主要用于 遍歷集合(如 List、數組、Map)生成一段重復的 SQL 語句,特別適合用于:
批量插入、批量刪除、
IN (...)
條件語句
1、 的核心作用
用來遍歷集合(如 List
、數組
、Map
),在 SQL 中動態生成重復片段,比如:
id IN (1, 2, 3)
多行
VALUES
插入多個字段拼接
2、常見屬性說明
屬性名 | 說明 |
---|---|
collection | 要遍歷的集合名,如 list 、array 、map 等 |
item | 遍歷過程中每一項的變量名 |
index | 當前項的下標(可選) |
open | 開始拼接的前綴(如 ( ) |
close | 拼接結束的后綴(如 ) ) |
separator | 每項之間的分隔符(如 , ) |
3、經典使用場景
(1). IN (...)
查詢
<select id="selectByIds" parameterType="list" resultType="User">SELECT * FROM userWHERE id IN<foreach collection="list" item="id" open="(" separator="," close=")">#{id}</foreach>
</select>
傳入:
List<Integer> ids = Arrays.asList(1, 2, 3);
生成 SQL:
SELECT * FROM user WHERE id IN (1, 2, 3)
(2). 批量插入
<insert id="insertUsers" parameterType="list">INSERT INTO user (username, age)VALUES<foreach collection="list" item="u" separator=",">(#{u.username}, #{u.age})</foreach>
</insert>
【注意】:
此時,屬性:open,close都沒有寫!?
傳入:
List<User> users = List.of(new User("Tom", 20),new User("Jerry", 22)
);
生成 SQL:
INSERT INTO user (username, age)
VALUES ('Tom', 20), ('Jerry', 22)
(3). 遍歷 Map,實現動態 UPDATE 字段
<foreach collection="map" item="val" index="key">${key} = #{val}
</foreach>
示例:
int updateForEach(@Param("updateFields") Map<String, Object> updateFields,@Param("id") Long id);
【注意】:
????????此時的key用的是${key},因為是數據庫字段,不能用#{key},否則拼接的數據庫字段會加上字符串!
4、常見坑點提示
問題 | 說明 |
---|---|
collection="list" | 如果參數是 List ,要寫 "list" (不是寫變量名) |
<foreach> 必須配合 open/close/separator | 否則 SQL 拼接可能出錯 |
item=#{} 中的 item 要和聲明保持一致 | 否則無法解析參數 |
避免用 ${} 插值 | 容易引發 SQL 注入問題 |
2-7、<include>標簽
MyBatis 中的 <include>
標簽用于 在 XML 映射文件中復用 SQL 片段,類似 Java 中的方法提取,可以提高 SQL 語句的復用性和可維護性。
1、使用場景
多個 SQL 中有相同字段、相同條件、重復的列名等
希望統一維護公共 SQL 內容,避免拷貝粘貼
2、基本語法
1. 定義可復用的 SQL 片段(使用 <sql>
標簽)
<sql id="baseColumnList">id, name, age, gender, email
</sql>
2. 引用 SQL 片段(使用 <include>
標簽)
<select id="selectAll" resultType="User">SELECT<include refid="baseColumnList" />FROM users
</select>
3、完整示例
<!-- 定義通用字段列表 -->
<sql id="baseColumnList">id, name, age, gender, email
</sql><!-- 使用 include 引用 -->
<select id="selectById" resultType="User">SELECT <include refid="baseColumnList" />FROM usersWHERE id = #{id}
</select>
4、refid 范圍說明
<sql id="...">
的id
必須在當前命名空間中唯一如果跨命名空間使用,需要使用
namespace.id
引用,例如:
<include refid="com.example.mapper.UserMapper.baseColumnList" />
?? 注意事項
不支持
<sql>
中再嵌套<sql>
或<include>
!!!