目录
一、先明确:什么时候必须用动态 SQL?
二、应用层动态 SQL(生产 99% 场景)
2.1 MyBatis 动态 SQL(标签体系 + 实战案例)
核心标签速览
实战场景 1:多条件分页查询(最经典)
1) Mapper 接口
2) 入参实体
3) Mapper.xml 动态 SQL(标准写法)
关键点解析
实战场景 2:动态更新(部分字段更新)
标准写法
标签
特性
拓展:用 自定义(万能替代 where/set)
实战场景 3:互斥条件(choose/when 多分支)
实战场景 4:IN 查询 + 遍历集合
语法说明
示例 1:IN 批量查询
示例 2:批量插入(高频实战)
实战场景 5:动态排序(前端传排序字段 + 升降序)
业务:前端传 sortField 排序字段、sortOrder 升降序(ASC/DESC)
安全避坑(重中之重)
实战场景 6:动态表名(分表 / 按月日志表)
2.2 MyBatis-Plus 动态 SQL(简化开发)
1) 多条件查询(等价上面 XML 多条件)
2) 动态更新(仅更新非空字段)
三、MySQL 原生动态 SQL(存储过程 + PREPARE)
语法
简单示例:动态条件查询存储过程
四、动态 SQL 核心风险 & 生产避坑(面试高频)
4.1 SQL 注入(最严重)
两种占位符区别(必背)
注入攻击演示
4.2 索引失效问题
4.3 性能问题
4.4 语法报错
五、面试高频问答(总结)
六、生产最佳实践总结
动态 SQL 核心是运行时拼接 SQL 语句,解决查询条件不固定、表名 / 列名动态、分页 / 排序动态等场景,MySQL 中主要分两类实现:
本文结合业务实战场景、MyBatis/MyBatis-Plus 主流框架、原生语法、性能 & 安全避坑逐一讲解。
一、先明确:什么时候必须用动态 SQL?
固定 SQL 无法满足的场景:
原则:能写静态 SQL 绝不使用动态 SQL,动态 SQL 有注入风险、索引失效、可读性差问题。
二、应用层动态 SQL(生产 99% 场景)
Java 生态以 MyBatis / MyBatis-Plus 为核心,是企业最常用方案。
2.1 MyBatis 动态 SQL(标签体系 + 实战案例)
MyBatis 内置一套OGNL 动态标签,无需手动拼接字符串,防注入、语法优雅。
核心标签速览
| <if> | 单条件判断(最常用) |
| <where> | 自动处理 and/or、去除首部多余 and |
| <trim> | 自定义截取前缀 / 后缀(替代 where/set) |
| <set> | 更新语句专用,去除尾部多余 , |
| <choose>/<when>/<otherwise> | 多分支互斥判断(类似 if-else if-else) |
| <foreach> | 遍历集合(in 查询、批量插入 / 更新) |
| <bind> | 绑定变量,拼接模糊查询 %,优化索引 |
实战场景 1:多条件分页查询(最经典)
业务:用户列表查询,前端可传:用户名 (模糊)、状态、手机号、开始时间、结束时间,条件非必选。
1) Mapper 接口
// 入参:查询实体
List<User> selectUserList(UserQuery query);
2) 入参实体
@Data
public class UserQuery {
private String username; // 用户名 模糊查询
private Integer status; // 状态 0禁用 1正常
private String phone; // 手机号
private LocalDateTime startTime; // 创建时间-开始
private LocalDateTime endTime; // 创建时间-结束
private Integer pageNum;
private Integer pageSize;
}
3) Mapper.xml 动态 SQL(标准写法)
<select id=\”selectUserList\” resultType=\”com.entity.User\”>
SELECT id, username, phone, status, create_time
FROM t_user
<!– 自动拼接 WHERE,剔除第一个多余 AND –>
<where>
<!– 用户名模糊查询:非空才拼接 –>
<if test=\”username != null and username != \’\’\”>
AND username LIKE CONCAT(\’%\’, #{username}, \’%\’)
</if>
<!– 状态:非null才拼接(0也是有效值,不能判空串) –>
<if test=\”status != null\”>
AND status = #{status}
</if>
<if test=\”phone != null and phone != \’\’\”>
AND phone = #{phone}
</if>
<!– 时间范围 –>
<if test=\”startTime != null\”>
AND create_time >= #{startTime}
</if>
<if test=\”endTime != null\”>
AND create_time <= #{endTime}
</if>
</where>
<!– 分页 –>
LIMIT #{pageNum}, #{pageSize}
</select>
关键点解析
- 所有条件为空时,自动省略 WHERE 关键字,不会语法报错
- 自动去掉条件最前面多余的 AND
- 字符串:!= null and != \’\’(空串视为无查询条件)
- 数值 / 日期:只判 != null(0、0.0 是合法业务值)
实战场景 2:动态更新(部分字段更新)
业务:编辑用户,前端只传修改的字段,空字段不更新(避免覆盖原有数据)。







