在数据库交互场景中,业务需求往往需要根据不同的条件动态生成SQL语句(例如:多条件筛选、可选排序、分页参数等)。MyBatis作为优秀的持久层框架,通过动态SQL功能提供了灵活的SQL拼接能力,避免了手动拼接字符串的繁琐与风险(如SQL注入)。本文将系统介绍MyBatis动态SQL的核心标签、典型场景实践及注意事项。
![图片[1]_MyBatis实现动态拼接SQL的实践指南_知途无界](https://zhituwujie.com/wp-content/uploads/2025/11/d2b5ca33bd20251123102213.png)
一、动态SQL的核心作用
动态SQL允许开发者根据运行时参数的值(如Java方法传入的条件对象属性是否为null、集合是否为空等),动态决定是否包含某段SQL片段、拼接条件或调整语句结构。其核心价值包括:
- 避免硬编码多套SQL:无需为不同条件组合编写重复的SQL语句。
- 防止SQL注入:通过MyBatis的参数绑定机制(如
#{})自动处理转义,比手动拼接字符串更安全。 - 提升可维护性:将条件逻辑集中在XML映射文件中,与Java代码解耦,便于后续扩展。
二、MyBatis动态SQL的核心标签
MyBatis提供了以下常用动态SQL标签(均位于XML映射文件的<sql>、<select>、<insert>、<update>、<delete>等标签内部使用):
| 标签 | 作用 | 典型使用场景 |
|---|---|---|
**<if>** | 条件判断:当test表达式为true时,包含内部的SQL片段 | 根据非空参数动态添加查询条件(如name不为null时加WHERE name=#{name}) |
**<choose> <when> <otherwise>** | 多路分支选择:类似Java的switch-case-default | 多个互斥条件中选择一个生效(如按优先级匹配不同字段) |
**<where>** | 智能处理WHERE子句:自动去除开头的AND/OR,或省略无条件的WHERE | 动态条件拼接时避免语法错误(如WHERE AND问题) |
**<set>** | 智能处理UPDATE语句的SET子句:自动去除结尾的逗号 | 动态更新部分字段时避免语法错误 |
**<foreach>** | 循环遍历集合:生成IN条件、批量插入等重复片段 | 根据ID列表查询(WHERE id IN (1,2,3))或批量插入数据 |
**<trim>** | 自定义字符串修剪:灵活去除前缀、后缀或多余字符 | 更复杂的条件拼接控制(如替代<where>的部分场景) |
三、典型场景实践示例
场景1:多条件动态查询(使用<if> + <where>)
需求:根据用户传入的UserQuery对象(包含name、age、status等可能为null的字段),动态生成查询SQL。
Java实体类(参数对象)
public class UserQuery {
private String name; // 可能为null
private Integer age; // 可能为null
private Integer status; // 可能为null(如1-启用,0-禁用)
// getter/setter省略
}
Mapper接口
List<User> selectByCondition(UserQuery query);
XML映射文件(动态SQL实现)
<select id="selectByCondition" resultType="User">
SELECT id, name, age, status
FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%') <!-- 模糊查询 -->
</if>
<if test="age != null">
AND age = #{age} <!-- 精确匹配年龄 -->
</if>
<if test="status != null">
AND status = #{status} <!-- 状态筛选 -->
</if>
</where>
ORDER BY create_time DESC <!-- 固定排序 -->
</select>
关键点解析:
<where>标签会自动处理开头的AND:如果所有<if>条件均未满足(即无动态条件),则不会生成WHERE关键字;若存在条件,会自动去除第一个条件前的AND。test表达式使用OGNL语法(类似Java语法),判断参数是否为null或空字符串。
场景2:多选一条件(使用<choose> <when> <otherwise>)
需求:根据用户传入的searchType(如”name”、”email”、”phone”),动态匹配不同字段进行查询。
Mapper接口
List<User> selectBySearchType(@Param("searchType") String searchType, @Param("keyword") String keyword);
XML映射文件
<select id="selectBySearchType" resultType="User">
SELECT id, name, email, phone
FROM user
WHERE 1=1
<choose>
<when test="searchType != null and searchType == 'name'">
AND name LIKE CONCAT('%', #{keyword}, '%')
</when>
<when test="searchType != null and searchType == 'email'">
AND email = #{keyword}
</when>
<when test="searchType != null and searchType == 'phone'">
AND phone = #{keyword}
</when>
<otherwise>
AND status = 1 <!-- 默认条件:只查启用的用户 -->
</otherwise>
</choose>
</select>
关键点解析:
<choose>类似于switch,内部的<when>按顺序判断,第一个满足条件的<when>会被执行,后续<when>忽略。<otherwise>是默认分支(类似default),当所有<when>均不满足时执行。
场景3:动态更新部分字段(使用<set>)
需求:根据用户传入的UserUpdateDTO对象(可能只包含需要更新的字段,如只改name或只改age),动态生成UPDATE语句,仅更新非null字段。
Java实体类(更新参数)
public class UserUpdateDTO {
private Long id; // 必须(主键)
private String name; // 可能为null(不更新)
private Integer age; // 可能为null(不更新)
// getter/setter省略
}
Mapper接口
int updateUserSelective(UserUpdateDTO dto);
XML映射文件
<update id="updateUserSelective">
UPDATE user
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
</set>
WHERE id = #{id}
</update>
关键点解析:
<set>标签会自动去除结尾多余的逗号(如只有一个字段更新时,不会生成name = ? ,)。- 必须包含
WHERE条件(如主键),否则会更新全表数据!
场景4:批量查询/删除(使用<foreach>)
需求:根据传入的ID列表(如List<Long> ids),查询或删除多个用户。
Mapper接口(批量查询)
List<User> selectByIds(@Param("ids") List<Long> ids);
XML映射文件
<select id="selectByIds" resultType="User">
SELECT id, name
FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
关键点解析:
collection:指定传入的集合参数名(需与Mapper接口方法参数名一致,或通过@Param注解指定,如@Param("ids"))。item:集合中每个元素的变量名(在SQL片段中通过#{item}引用,此处为#{id})。open/close:循环生成的片段的前缀和后缀(此处为(和))。separator:元素之间的分隔符(此处为,)。
批量删除示例(类似逻辑):
<delete id="deleteByIds">
DELETE FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
四、高级技巧与注意事项
1. 参数传递规范
- 若XML中引用了多个参数(如
name、age),建议在Mapper接口方法中使用@Param注解明确参数名,避免混淆:List<User> selectByCondition(@Param("name") String name, @Param("age") Integer age); - 在XML的
test表达式中,通过@Param指定的名称引用参数(如test="name != null")。
2. 避免常见错误
- **
<where>与手动AND**:若不用<where>,需手动处理第一个条件的AND(不推荐):<select id="badExample"> SELECT * FROM user WHERE 1=1 <!-- 强制所有条件前加AND,通过1=1保证语法正确 --> <if test="name != null">AND name = #{name}</if> </select>推荐优先使用<where>简化逻辑。 - **
<foreach>集合为空**:若传入的集合为null或空,生成的SQL可能包含IN ()(语法错误)。可通过<if>提前判断:<if test="ids != null and !ids.isEmpty()"> AND id IN <foreach collection="ids" item="id" ...>...</foreach> </if>
3. 复杂逻辑扩展
- **
<trim>替代<where>**:当需要更灵活的修剪逻辑时(如去除自定义前缀),可使用<trim>:<trim prefix="WHERE" prefixOverrides="AND |OR "> <if test="name != null">AND name = #{name}</if> </trim>prefixOverrides指定要去除的前缀(支持正则,如AND |OR会去除开头的AND或OR及空格)。 - 嵌套动态SQL:标签可嵌套使用(如在
<if>内部再嵌套<foreach>),实现更复杂的条件组合。
五、总结
MyBatis动态SQL通过<if>、<choose>、<where>、<foreach>等标签,将条件逻辑从Java代码转移到XML映射文件中,实现了安全、灵活的SQL拼接。开发者应根据实际场景选择合适的标签组合:
- 多条件筛选:优先用
<if>+<where>; - 多选一匹配:用
<choose>; - 批量操作:用
<foreach>; - 部分字段更新:用
<set>。
合理使用动态SQL,不仅能减少重复代码,还能提升SQL的可维护性与安全性,是MyBatis开发中的核心技能之一。

























暂无评论内容