文章目录
-
- [**1. if:最常用的条件拼接**](#1. if:最常用的条件拼接)
- [**2. where:自动处理多余的 AND / OR**](#2. where:自动处理多余的 AND / OR)
- [**3. set:专门给 UPDATE 用,自动去掉最后一个逗号**](#3. set:专门给 UPDATE 用,自动去掉最后一个逗号)
- [**4. choose / when / otherwise:多分支二选一**](#4. choose / when / otherwise:多分支二选一)
- [**5. trim:通用修整器,where 和 set 都是它的特化版**](#5. trim:通用修整器,where 和 set 都是它的特化版)
- [**6. foreach:批量参数、IN 查询、批量插入的核心**](#6. foreach:批量参数、IN 查询、批量插入的核心)
- [**7. bind:先绑定变量,再复用**](#7. bind:先绑定变量,再复用)
- [**8. script:注解方式下的动态 SQL**](#8. script:注解方式下的动态 SQL)
- [**9. 参数访问规则**](#9. 参数访问规则)
- [**11. #{} 和 {} 的区别\*\*](#{} 和 {} 的区别**)
- [**12. 常见坑**](#12. 常见坑)
核心概念
MyBatis 的"动态 SQL 标签"本质上就是:根据入参,按条件拼出最终 SQL。
它解决的是这类问题:
- 查询条件有时传、有时不传
- WHERE 后面的 AND/OR 容易多出来
- UPDATE 时只更新非空字段
- IN 查询参数个数不固定
- 多分支 SQL 需要二选一、三选一
MyBatis 动态 SQL 主要依赖 XML 标签和 test 表达式。test 用的是 OGNL 表达式。
1. if:最常用的条件拼接
sql
<select id="selectUser" resultType="User">
SELECT * FROM user
WHERE 1 = 1
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</select>
作用:
- test 为真,标签内 SQL 生效
- test 为假,整段不拼接
适合:
- 单个条件是否参与查询
- 非空才更新字段
- 按参数控制排序、过滤、范围
常见坑:
- 字符串判空不能只写 name != null,很多场景还要加 name != ''
- 0、false 不是 null,不要误判
- if 太多时,SQL 可读性会变差
2. where:自动处理多余的 AND / OR
sql
<select id="selectUser" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
它会做两件事:
- 只要内部有内容,就自动加 WHERE
- 自动去掉开头多余的 AND 或 OR
比如上面如果只有 age 生效,最终 SQL 会是:
sql
SELECT * FROM user WHERE age = ?
而不是:
sql
SELECT * FROM user WHERE AND age = ?
这比手写 WHERE 1=1 更干净。
3. set:专门给 UPDATE 用,自动去掉最后一个逗号
sql
<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
- 自动去掉最后多余的 ,
最终不会出现:
sql
UPDATE user SET name = ?, age = ?, WHERE id = ?
这是"按非空字段更新"的标准写法。
4. choose / when / otherwise:多分支二选一
类似 Java 里的 switch / if-else if-else。
sql
<select id="queryUser" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="name != null and name != ''">
name = #{name}
</when>
<otherwise>
status = 1
</otherwise>
</choose>
</where>
</select>
规则:
- 只会进入第一个命中的 when
- 如果都不命中,就走 otherwise
适合:
- "优先按 ID 查,否则按 name 查,否则查默认数据"
- 多条件互斥的业务逻辑
5. trim:通用修整器,where 和 set 都是它的特化版
sql
<select id="selectUser" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</trim>
</select>
含义:
- prefix="WHERE":有内容时前面加 WHERE
- prefixOverrides="AND |OR ":去掉开头的 AND / OR
更新场景也能用:
sql
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
</trim>
适合:
- 你觉得 where / set 不够灵活时
- 需要自定义前缀、后缀、去除规则
6. foreach:批量参数、IN 查询、批量插入的核心
查询 IN:
sql
<select id="selectByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
如果 ids = 1,2,3,最终 SQL 类似:
sql
SELECT * FROM user WHERE id IN (?, ?, ?)
批量插入:
sql
<insert id="batchInsert">
INSERT INTO user(name, age)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.age})
</foreach>
</insert>
常用属性:
- collection:遍历对象名
- item:当前元素变量名
- index:下标或 key
- open:开始符号
- separator:分隔符
- close:结束符号
常见坑:
- collection 名字要和入参对应
- 单个 List 参数常见是 list
- 单个数组参数常见是 array
- 使用 @Param("ids") 后就写 ids
- 空集合会生成非法 SQL
例如 IN (),要在业务层或 XML 中提前处理
7. bind:先绑定变量,再复用
sql
<select id="selectByNameLike" resultType="User">
<bind name="keyword" value="'%' + name + '%'" />
SELECT * FROM user
WHERE name LIKE #{keyword}
</select>
作用:
- 先用 OGNL 生成一个新变量
- 后续 SQL 中可直接用这个变量
典型用途:
- LIKE 模糊查询
- 对参数做轻量预处理
- 避免把拼接逻辑写得很乱
比直接写 ${} 安全得多。
8. script:注解方式下的动态 SQL
如果你不是写 XML,而是写 Mapper 注解,可以用 :
java
@Select("""
<script>
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</script>
""")
List<User> selectUser(@Param("name") String name, @Param("age") Integer age);
作用:
- 让注解中的 SQL 也支持动态标签
但实际项目里,复杂 SQL 还是更推荐 XML,维护性更好。
9. 参数访问规则
这是很多人最容易混乱的地方。
单个普通参数
java
User selectById(Long id);
XML 中一般可直接用:
sql
#{id}
有时也可通过 _parameter 访问。
多个参数
java
List<User> query(@Param("name") String name, @Param("age") Integer age);
XML 中:
sql
name != null
#{name}
#{age}
对象参数
java
List<User> query(UserQuery query);
XML 中可直接写对象属性:
sql
query.name // 某些场景
name // 更常见
age
集合参数
java
List<User> selectByIds(@Param("ids") List<Long> ids);
XML 中:
sql
<foreach collection="ids" item="id">
建议:
- 多参数时始终用 @Param
- 集合参数也显式命名,少踩坑
11. #{} 和 ${} 的区别
#{}
- 预编译占位符
- 自动加引号、类型处理
- 安全,推荐默认使用
${}
- 直接文本替换
- 不会预编译
- 有 SQL 注入风险
例子:
sql
WHERE name = #{name}
如果 name = "Tom",会变成参数化 SQL。
而:
sql
ORDER BY ${column}
会把 column 原样拼到 SQL 里。
结论:
- 值用 #{}
- 表名、列名、排序字段这类"SQL 标识符"才可能用 ${}
- 用 ${} 时必须白名单校验
12. 常见坑
1. where 里不要自己乱写固定 AND
让 where/trim 去处理前缀更稳。
2. foreach 空集合问题
如果集合为空,IN () 会报错。常见做法:
- 业务层提前返回空结果
- 或 XML 中先判断 list != null and list.size > 0
3. LIKE 不要直接用 ${}
错误示例:
sql
name LIKE '%${name}%'
推荐:
sql
<bind name="keyword" value="'%' + name + '%'" />
name LIKE #{keyword}
4. 更新时漏掉 WHERE
动态 set 很方便,但 WHERE id = #{id} 不能漏,不然可能全表更新。
5. test 表达式写错属性名
OGNL 访问的是参数对象属性,不是数据库字段名。