在实际业务开发中,数据库查询条件几乎不会一成不变:前端多条件筛选、非必填参数、动态排序、分页拼接、批量增删改等场景,固定写死的SQL语句完全无法适配业务需求。
MyBatis 动态SQL 是其核心核心能力之一,区别于静态SQL,可根据参数是否为空、参数值、业务状态自动拼接、裁剪、优化SQL语句,彻底解决多条件动态查询、批量操作、条件分支适配问题。
本文将从核心原理、全套标签详解、场景Demo、对比表格、实战规范、常见坑点全方位梳理,覆盖99%企业开发动态SQL场景。
一、动态SQL核心原理与优势
1.1 核心原理
MyBatis 在执行SQL前,会通过OGNL表达式解析XML中的动态标签,根据传入的参数对象属性值,动态判断是否拼接SQL片段,最终生成一条合规、无语法错误的可执行SQL,再交由数据库执行。
1.2 动态SQL vs 静态SQL 对比
| 对比维度 | 静态SQL | 动态SQL |
|---|---|---|
| 写法特点 | SQL语句固定写死,无逻辑判断 | 通过标签+表达式动态拼接SQL片段 |
| 参数适配性 | 参数缺失会报SQL语法错误、查询异常 | 自动忽略空参数,适配非必填条件 |
| 业务场景 | 仅适用于固定条件查询、简单CRUD | 多条件筛选、批量操作、动态排序/分页 |
| 维护成本 | 多场景需写多条SQL,冗余极高 | 一条SQL适配全场景,统一维护 |
| 语法安全性 | 易出现多余and/or、逗号等语法问题 | 内置语法裁剪机制,杜绝语法错误 |
1.3 动态SQL全套核心标签总览
| 标签 | 核心作用 | 适用场景 |
|---|---|---|
| if | 单条件判断,满足条件则拼接SQL | 非必填查询条件、参数动态拼接 |
| where | 自动去除多余的 and/or,智能拼接where关键字 | 多if条件组合查询,解决语法冗余问题 |
| trim | 自定义前后缀、去除指定冗余字符,高度灵活 | where/set无法满足的特殊裁剪场景 |
| set | 动态拼接update字段,自动去除末尾多余逗号 | 动态更新字段(部分字段更新) |
| choose/when/otherwise | 多条件互斥分支,只执行一个匹配条件 | 单选条件筛选(优先级判断) |
| foreach | 遍历集合、数组,批量拼接SQL片段 | 批量查询、批量新增、批量删除 |
| bind | 绑定自定义变量,简化表达式、防止SQL注入 | 模糊查询、复杂参数处理 |
| include/sql | 抽取通用SQL片段,复用代码 | 公共字段、通用查询条件复用 |
二、全标签深度解析 + 可直接运行Demo
统一前置实体与参数:本文所有Demo基于 User 实体类 和 user 数据表,适配SpringBoot + MyBatis 常规项目。
// User实体类
public class User {
private Long id;
private String username;
private Integer age;
private String phone;
private Integer status; // 状态 0-禁用 1-正常
private LocalDateTime createTime;
// getter/setter 省略
}
// 前端查询参数DTO(动态查询入参)
public class UserQuery {
private String username; // 模糊查询
private Integer age; // 精准查询
private Integer status; // 状态筛选
private List<Long> idList; // 批量ID
// getter/setter 省略
}
2.1 if 标签:基础单条件动态拼接
作用:判断参数是否非空,满足条件则拼接对应SQL,最基础、使用频率最高的动态标签。
语法规则:test属性为OGNL表达式,支持非空判断、数值判断、字符串判断。
<!-- 多条件动态查询用户 -->
<select id="listUserByCondition" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
SELECT id, username, age, phone, status, create_time
FROM user
WHERE del_flag = 0
<!-- 用户名非空则模糊查询 -->
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<!-- 年龄不为空则精准查询 -->
<if test="age != null">
AND age = #{age}
</if>
<!-- 状态不为空则筛选 -->
<if test="status != null">
AND status = #{status}
</if>
</select>
坑点注意 :单纯使用if标签,首个条件为空时,会残留多余的 AND,导致SQL语法报错,需配合 where标签 使用。
2.2 where 标签:智能处理查询条件前缀
核心优势 :1. 自动识别并添加where关键字;2. 自动去除第一个条件前多余的 and/or;3. 无任何条件时,不生成where语句。
完美解决if标签单独使用的语法报错问题,企业开发查询场景必用。
<select id="listUserByCondition" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
SELECT id, username, age, phone, status, create_time
FROM user
<where>
del_flag = 0
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
2.3 trim 标签:自定义语法裁剪(万能替代where/set)
作用:自定义拼接前缀、后缀,同时裁剪指定的首尾多余字符,是动态SQL的万能语法修复标签。
核心属性:
prefix:整体拼接的前缀字符串
suffix:整体拼接的后缀字符串
prefixOverrides:需要去除的首部字符(and/or)
suffixOverrides:需要去除的尾部字符(逗号)
Demo:trim实现where标签效果
<select id="listUserByTrim" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
SELECT id, username, age, phone, status, create_time
FROM user
<trim prefix="WHERE" prefixOverrides="AND|OR">
del_flag = 0
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</trim>
</select>
2.4 set 标签:动态更新字段专用
场景:后台修改用户信息时,只更新传入的字段,空字段不更新,避免覆盖原有数据。
核心优势 :自动去除最后一个字段后的多余逗号,彻底解决动态更新的语法报错问题。
<update id="updateUserDynamic" parameterType="com.xxx.entity.User">
UPDATE user
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="phone != null and phone != ''">
phone = #{phone},
</if>
<if test="status != null">
status = #{status}
</if>
</set>
WHERE id = #{id} AND del_flag = 0
</update>
2.5 choose/when/otherwise:互斥分支判断
核心特点 :多条件单选互斥 ,自上而下匹配,匹配成功一个分支后,不再执行后续分支,类似 Java 的 if-else if-else。
适用场景:优先级筛选、唯一条件匹配(如:优先ID查询,无ID则用户名查询,最后查全部)
<select id="getUserByPriority" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
SELECT id, username, age, status
FROM user
<where>
del_flag = 0
<choose>
<!-- 优先根据ID精准查询 -->
<when test="id != null">
AND id = #{id}
</when>
<!-- ID为空则根据用户名查询 -->
<when test="username != null and username != ''">
AND username = #{username}
</when>
<!-- 所有条件为空,默认查询正常状态用户 -->
<otherwise>
AND status = 1
</otherwise>
</choose>
</where>
</select>
2.6 foreach 标签:批量操作核心(高频必考)
核心场景:批量删除、批量新增、IN集合查询、批量更新。
核心属性说明:
| 属性 | 作用 |
|---|---|
| collection | 遍历集合/数组参数名(List填list、数组填array、自定义参数填参数名) |
| item | 遍历后的单个元素别名 |
| index | 遍历下标/Map键名 |
| open | 遍历整体前缀(如 IN ( 的左括号) |
| close | 遍历整体后缀(如 ) 的右括号) |
| separator | 元素之间的分隔符(逗号、OR等) |
Demo1:IN 批量查询(根据ID集合查询用户)
<select id="listUserByIdList" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
SELECT * FROM user WHERE del_flag = 0
<if test="idList != null and idList.size() > 0">
AND id IN
<foreach collection="idList" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</if>
</select>
Demo2:批量删除用户
<delete id="batchDeleteUser" parameterType="java.util.List">
DELETE FROM user WHERE id IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
Demo3:批量新增用户(高性能)
<insert id="batchInsertUser" parameterType="java.util.List">
INSERT INTO user (username, age, phone, status, create_time)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.age}, #{user.phone}, #{user.status}, NOW())
</foreach>
</insert>
2.7 bind 标签:参数绑定与模糊查询优化
作用 :自定义绑定变量,简化OGNL表达式,解决数据库模糊查询兼容性问题(MySQL、Oracle适配),同时预防SQL注入。
<select id="listUserByLike" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
<!-- 绑定模糊查询变量,全局复用 -->
<bind name="likeUsername" value="'%' + username + '%'"/>
SELECT * FROM user
<where>
del_flag = 0
<if test="username != null and username != ''">
AND username LIKE #{likeUsername}
</if>
</where>
</select>
2.8 sql + include:SQL片段复用
作用:抽取通用SQL片段(公共字段、通用查询条件),避免代码冗余,统一维护。
<!-- 抽取公共查询字段 -->
<sql id="userCommonField">
id, username, age, phone, status, create_time
</sql>
<!-- 抽取通用删除条件 -->
<sql id="delFlagCondition">
del_flag = 0
</sql>
<select id="listAllUser" resultType="com.xxx.entity.User">
SELECT <include refid="userCommonField"/>
FROM user
<where>
<include refid="delFlagCondition"/>
</where>
</select>
三、动态SQL高频场景整合Demo
3.1 综合多条件筛选(if+where+bind)
适配:用户名模糊、年龄区间、状态筛选,全非必填参数
<select id="listUserComplexQuery" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
<bind name="likeName" value="'%' + username + '%'"/>
SELECT id, username, age, status, create_time
FROM user
<where>
del_flag = 0
<if test="username != null and username != ''">
AND username LIKE #{likeName}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
3.2 动态字段更新(set+if)
只更新传入的非空字段,保留数据库原有旧数据
<update id="updateUserSelective" parameterType="com.xxx.entity.User">
UPDATE user
<set>
<if test="username != null and username != ''">username=#{username},</if>
<if test="age != null">age=#{age},</if>
<if test="phone != null and phone != ''">phone=#{phone},</if>
<if test="status != null">status=#{status},</if>
update_time = NOW()
</set>
WHERE id = #{id} AND del_flag = 0
</update>
四、动态SQL核心避坑指南(高频报错)
| 常见坑点 | 报错原因 | 解决方案 |
|---|---|---|
| 多余AND/OR语法错误 | 首个if条件为空,残留前置AND | 所有多条件查询统一使用 <where> 标签 |
| UPDATE末尾多余逗号 | 动态字段最后一条拼接逗号 | 更新语句统一使用 <set> 标签 |
| foreach空集合报错 | 集合为空时生成 IN() 空语法 | 遍历前加判断:size>0 |
| 字符串空串判断遗漏 | 只判断null,未判断空字符串,导致无效查询 | 字符串统一判断:str != null and str != '' |
| choose多分支同时生效 | 误用if替代choose,未理解互斥逻辑 | 单选条件必须使用choose,不可用多个if |
五、企业开发动态SQL规范总结
查询场景 :多条件非必填查询,固定搭配
where + if,杜绝手写where关键字更新场景 :局部动态更新,必须使用
set + if,防止逗号语法错误单选分支 :优先级、互斥条件,强制使用
choose/when/otherwise批量操作 :所有集合遍历必须用
foreach,且前置非空判断代码复用 :公共字段、通用条件统一抽取
sql片段,全局include引用参数判断:数值型只判null,字符串必须同时判null和空串
六、全文总结
MyBatis动态SQL的核心价值是适配业务不确定性,通过8大核心标签,可完美解决:多条件筛选、局部更新、批量操作、分支查询等所有复杂业务场景。
开发核心口诀:查询用where、更新用set、单选用choose、批量用foreach、复用抽sql、空参必判断。
掌握本文所有Demo与避坑要点,可完全覆盖企业开发中99%的动态SQL开发场景,杜绝语法报错与代码冗余问题。