一文詳解MyBatis中動態(tài)SQL的封裝原理與常用標(biāo)簽實(shí)戰(zhàn)應(yīng)用
告別繁瑣的SQL拼接,讓動態(tài)查詢像搭積木一樣簡單
一、為什么需要動態(tài)SQL?
在實(shí)際開發(fā)中,我們經(jīng)常會遇到需要根據(jù)不同的條件拼接SQL語句的場景。傳統(tǒng)的做法是使用Java代碼進(jìn)行字符串拼接,但這種方式不僅代碼冗余、易出錯,還存在SQL注入的風(fēng)險。MyBatis提供的動態(tài)SQL機(jī)制,允許我們在XML映射文件中使用標(biāo)簽來構(gòu)建靈活的SQL語句,讓代碼更清晰、更安全。
核心優(yōu)勢:
- 無需手動處理多余的空格、AND/OR、逗號分隔符
- 基于OGNL表達(dá)式,與Java對象無縫集成
- 提升可維護(hù)性,SQL結(jié)構(gòu)一目了然
二、動態(tài)SQL的執(zhí)行流程
MyBatis在解析動態(tài)SQL時,會根據(jù)傳入的參數(shù)動態(tài)生成最終的SQL語句。下圖展示了從XML到預(yù)編譯SQL的完整過程:

三、常用動態(tài)SQL標(biāo)簽詳解
1.<if>– 條件判斷
最簡單的條件標(biāo)簽,當(dāng)條件滿足時拼接SQL片段。
語法:
<if test="條件表達(dá)式">
SQL片段
</if>示例: 根據(jù)姓名和年齡動態(tài)查詢用戶
<select id="findUsers" resultType="User">
SELECT * FROM user WHERE 1=1
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null and age > 0">
AND age = #{age}
</if>
</select>注意: WHERE 1=1 是為了防止所有條件都不滿足時出現(xiàn)語法錯誤。更好的替代方案是使用 <where> 標(biāo)簽。
2.<where>– 智能處理查詢條件
自動處理AND/OR關(guān)鍵字,并去除多余的連接詞。
示例: 重構(gòu)上面的查詢
<select id="findUsers" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null and age > 0">
AND age = #{age}
</if>
</where>
</select>當(dāng) name 有值而 age 為 null 時,生成的SQL為:
SELECT * FROM user WHERE name = ?<where>標(biāo)簽會自動去掉開頭的AND。
3.<set>– 動態(tài)更新字段
在 <update> 語句中智能處理逗號分隔符。
示例: 動態(tài)更新用戶信息
<update id="updateUser">
UPDATE user
<set>
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
<if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}
</update>
<set> 會自動去掉末尾多余的逗號,并保證至少有一個字段被更新。
4.<foreach>– 遍歷集合/數(shù)組
常用于 IN 查詢、批量插入、批量刪除。
屬性說明:
| 屬性 | 作用 |
|---|---|
collection | 要遍歷的集合/數(shù)組名稱 |
item | 每次迭代的當(dāng)前元素別名 |
index | 當(dāng)前元素的索引(或map的key) |
open | 遍歷開始拼接的字符串 |
close | 遍歷結(jié)束拼接的字符串 |
separator | 元素之間的分隔符 |
示例1: 批量查詢(IN條件)
<select id="findByIds" resultType="User">
SELECT * FROM user WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
示例2: 批量插入
<insert id="batchInsert">
INSERT INTO user (name, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert>
常見坑點(diǎn): collection 默認(rèn)支持 list、array、map。如果是自定義參數(shù)名,需要使用 @Param 注解指定。
5.<trim>– 自定義前綴/后綴處理
<where> 和 <set> 本質(zhì)上都是 <trim> 的快捷方式,當(dāng)需要更精細(xì)的控制時可直接使用 <trim>。
屬性:
prefix– 整體前添加的字符串suffix– 整體后添加的字符串prefixOverrides– 去除前綴中的指定字符串suffixOverrides– 去除后綴中的指定字符串
等價關(guān)系:
<!-- <where> 等價于 -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
<!-- <set> 等價于 -->
<trim prefix="SET" suffixOverrides=",">
...
</trim>
自定義示例: 動態(tài)添加 ORDER BY 子句
<select id="selectWithOrder" resultType="User">
SELECT * FROM user
<trim prefix="ORDER BY" suffixOverrides=",">
<if test="orderByName">name,</if>
<if test="orderByAge">age,</if>
</trim>
</select>
6.<choose> / <when> / <otherwise>– 分支選擇
類似Java的 switch-case,只執(zhí)行第一個滿足條件的分支。

示例: 按優(yōu)先級排序查詢(姓名優(yōu)先,否則年齡,否則默認(rèn))
<select id="findUsersWithPriority" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
</when>
<when test="age != null">
AND age = #{age}
</when>
<otherwise>
AND status = 'ACTIVE'
</otherwise>
</choose>
</where>
</select>
四、綜合實(shí)戰(zhàn):員工管理系統(tǒng)動態(tài)查詢
下面是一個結(jié)合多個標(biāo)簽的完整示例:
<mapper namespace="com.example.EmployeeMapper">
<!-- 動態(tài)查詢員工 -->
<select id="searchEmployees" resultType="Employee">
SELECT * FROM employee
<where>
<if test="deptId != null">
AND dept_id = #{deptId}
</if>
<if test="minSalary != null">
AND salary >= #{minSalary}
</if>
<if test="maxSalary != null">
AND salary <= #{maxSalary}
</if>
<if test="keywords != null and keywords != ''">
AND (name LIKE CONCAT('%', #MyBatis動態(tài)SQL封裝,MyBatis動態(tài)SQL使用,MyBatis動態(tài)SQL,MyBatis?SQL, '%')
OR position LIKE CONCAT('%', #MyBatis動態(tài)SQL封裝,MyBatis動態(tài)SQL使用,MyBatis動態(tài)SQL,MyBatis?SQL, '%'))
</if>
<choose>
<when test="hireDateStart != null and hireDateEnd != null">
AND hire_date BETWEEN #{hireDateStart} AND #{hireDateEnd}
</when>
<when test="hireDateStart != null">
AND hire_date >= #{hireDateStart}
</when>
<when test="hireDateEnd != null">
AND hire_date <= #{hireDateEnd}
</when>
</choose>
</where>
<if test="orderBy != null">
<trim prefix="ORDER BY" suffixOverrides=",">
<if test="orderBy.name">name,</if>
<if test="orderBy.salary">salary,</if>
<if test="orderBy.hireDate">hire_date,</if>
</trim>
</if>
</select>
</mapper>
五、最佳實(shí)踐與常見問題
推薦做法
- 優(yōu)先使用
<where>/<set>而非手寫1=1或SET后跟逗號。 - 結(jié)合
<trim>處理復(fù)雜的動態(tài)前綴/后綴邏輯。 - 使用
@Param為集合參數(shù)命名,避免依賴默認(rèn)的list/array。 - 對字符串判空:
test="name != null and name != ''"。
常見錯誤
| 錯誤現(xiàn)象 | 原因 | 解決方案 |
|---|---|---|
| 動態(tài)SQL不生效 | 忘記在<select>中嵌入動態(tài)標(biāo)簽 | 檢查標(biāo)簽是否正確嵌套 |
| 多余的AND/OR | 條件未全部滿足時前綴殘留 | 使用<where>或<trim prefixOverrides> |
| 批量插入性能差 | foreach生成超大SQL | 改用分批處理或JDBC batch |
| OGNL表達(dá)式異常 | 使用了Java的&&/` |
六、總結(jié)
MyBatis的動態(tài)SQL通過一套簡潔的XML標(biāo)簽,完美解決了傳統(tǒng)JDBC拼接SQL的痛點(diǎn)。掌握以下核心標(biāo)簽即可應(yīng)對90%的動態(tài)場景:
| 標(biāo)簽 | 核心用途 |
|---|---|
<if> | 簡單條件判斷 |
<where> | 智能查詢條件組裝 |
<set> | 智能更新字段組裝 |
<foreach> | 遍歷集合(IN、批量操作) |
<trim> | 自定義前綴/后綴處理 |
<choose> | 多分支互斥選擇 |
動態(tài)SQL的本質(zhì)是在XML層面基于OGNL表達(dá)式進(jìn)行零邏輯的SQL片段組合。理解這一思想后,你可以靈活組合這些標(biāo)簽,寫出既優(yōu)雅又安全的動態(tài)SQL。
延伸思考: 如果動態(tài)條件非常復(fù)雜(超過5個分支),建議考慮將部分邏輯遷移到Java層,使用@SelectProvider注解或構(gòu)建查詢對象模式,以保持XML的可讀性。
到此這篇關(guān)于一文詳解MyBatis中動態(tài)SQL的封裝原理與常用標(biāo)簽實(shí)戰(zhàn)應(yīng)用的文章就介紹到這了,更多相關(guān)MyBatis動態(tài)SQL應(yīng)用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springcloud 熔斷監(jiān)控Hystrix Dashboard和Turbine
這篇文章主要介紹了springcloud 熔斷監(jiān)控Hystrix Dashboard和Turbine,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
基于SpringMVC攔截器實(shí)現(xiàn)接口耗時監(jiān)控功能
本文呢主要介紹了基于SpringMVC攔截器實(shí)現(xiàn)的接口耗時監(jiān)控功能,統(tǒng)計接口的耗時情況屬于一個可以復(fù)用的功能點(diǎn),因此這里直接使用 SpringMVC的HandlerInterceptor攔截器來實(shí)現(xiàn),需要的朋友可以參考下2024-02-02
Java多數(shù)據(jù)源的三種實(shí)現(xiàn)方式小結(jié)
多數(shù)據(jù)源是在一個應(yīng)用程序中配置和使用多個不同的數(shù)據(jù)庫連接,本文主要介紹了Java多數(shù)據(jù)源的三種實(shí)現(xiàn)方式小結(jié),具有一定的參考價值,感興趣的可以了解一下2025-03-03
FileOutputStream文件寫入類實(shí)現(xiàn)方式
這篇文章主要介紹了FileOutputStream文件寫入類實(shí)現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2026-03-03
Spring boot 數(shù)據(jù)庫連接斷線重連問題
這篇文章主要介紹了Spring boot 數(shù)據(jù)庫連接斷線重連問題,需要的朋友可以參考下2017-06-06

