目录
一、先明确:什么时候必须用动态 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 中主要分两类实现:
- 应用层动态 SQL(Java/PHP 等代码拼接,主流)
- MySQL 原生动态 SQL(存储过程 +
PREPARE/EXECUTE,少用)
本文结合业务实战场景、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 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>优势:- 所有条件为空时,自动省略 WHERE 关键字,不会语法报错
- 自动去掉条件最前面多余的
AND
- 判断区别:
- 字符串:
!= null and != ''(空串视为无查询条件) - 数值 / 日期:只判
!= null(0、0.0 是合法业务值)
- 字符串:
- 模糊查询:用
CONCAT('%',#{val},'%'),不要在 Java 代码拼接 %。
实战场景 2:动态更新(部分字段更新)
业务:编辑用户,前端只传修改的字段,空字段不更新(避免覆盖原有数据)。