MyBatis 动态 SQL 详解:从基础到进阶实战
在 MyBatis 开发中,静态 SQL 往往无法满足复杂的业务场景(比如多条件查询、部分字段更新),而动态 SQL通过标签灵活拼接 SQL 语句,完美解决了这一痛点。本文结合实战案例,带你从基础到进阶掌握 MyBatis 动态 SQL 的核心用法,并补充数组入参、实体类构造函数的关键细节。
一、动态 SQL 核心场景 1:多条件查询(<where>+<if>)
当需要根据用户传入的非必填参数 (如用户名、地址)拼接查询条件时,静态 SQL 会出现 "多余的 AND/OR" 或 "条件缺失" 问题,而<where>+<if>可以自动处理这些细节。
1. 代码示例(Mapper.xml)
xml
<select id="dtfindUser" resultType="entity.User" parameterType="entity.User">
select * from user
<where>
<!-- 用户名不为空时,拼接模糊查询条件 -->
<if test="username!=null">
username like concat('%',#{username},'%')
</if>
<!-- 性别不为空时,拼接AND + 性别条件 -->
<if test="sex!=null">
and sex=#{sex}
</if>
<!-- 地址不为空时,拼接AND + 地址条件 -->
<if test="address!=null">
and address=#{address}
</if>
</where>
</select>
2. 测试代码与执行效果
java
运行
@Test
public void dtfindUsersTest(){
User user=new User();
user.setUsername("小"); // 传用户名
user.setAddress("上海"); // 传地址
List<User> users=mapper.dtfindUser(user);
}
最终执行的 SQL(自动拼接where和and):
sql
select * from user WHERE username like concat('%',?,'%') and address=?

二、动态 SQL 核心场景 2:部分字段更新(<set>+<if>)
更新操作中,若只修改部分字段(如仅改用户名),静态 SQL 会将其他字段设为null,而<set>+<if>可自动保留 "有值的字段" 并处理逗号。
1. 问题:静态 SQL 的缺陷
若直接写update user set username=?,sex=?,address=? where id=?,但只给username赋值,最终执行的 SQL 会把sex、address设为null:
sql
update user set username = ?, birthday = ?,sex ?,address=? where id = ?
Parameters: 老王(String), null, null, null, 16(Integer)
2. 动态 SQL 解决方案(Mapper.xml)
xml
<update id="dtUpdate">
update user
<set>
<!-- 用户名不为空时,拼接"username=值," -->
<if test="username!=null">
username=#{username},
</if>
<!-- 性别不为空时,拼接"sex=值," -->
<if test="sex!=null">
sex=#{sex},
</if>
<!-- 地址不为空时,拼接"address=值," -->
<if test="address!=null">
address=#{address},
</if>
<!-- 生日不为空时,拼接"birthday=值"(无逗号) -->
<if test="birthday!=null">
birthday=#{birthday}
</if>
</set>
where id=#{id}
</update>
3. 测试代码与执行效果
java
运行
@Test
public void dtUpdate(){
User user=new User();
user.setUsername("你好"); // 仅传用户名
user.setId("20");
int count = mapper.dtUpdate(user);
}
最终执行的 SQL(仅更新username):
sql
update user SET username=? WHERE id=?
Parameters: 你好(String), 20(String)




三、进阶:<trim>标签 ------ 更灵活的 SQL 拼接
<trim>是<where>和<set>的 "超集",可以自定义前缀、后缀 及要移除的多余字符,适用于更复杂的场景。
1. 替代<where>:处理多余的and/or
xml
<select id="trfindUser" resultType="entity.User" parameterType="entity.User">
select * from user
<!-- prefix:添加前缀where;prefixOverrides:移除开头的and/or -->
<trim prefix="where" prefixOverrides="and|or">
<if test="username!=null">
username like concat('%',#{username},'%')
</if>
<if test="sex!=null">
and sex=#{sex}
</if>
<if test="address!=null">
and address=#{address}
</if>
</trim>
</select>
2. 替代<set>:处理多余的逗号
xml
<update id="trUpdate">
update user
<!-- prefix:添加前缀set;suffixOverrides:移除结尾的逗号 -->
<trim prefix="set" suffixOverrides=",">
<if test="username!=null">
username=#{username},
</if>
<if test="sex!=null">
sex=#{sex},
</if>
<if test="address!=null">
address=#{address},
</if>
</trim>
where id=#{id}
</update>


四、分支选择:<choose>+<when>+<otherwise>
类似 Java 的if-else if-else,仅执行第一个满足条件的分支,适用于 "多条件互斥" 的场景(比如 "按用户名查,或按地址查,否则按 ID 查")。
1. Mapper.xml 示例
xml
<select id="selectUserByChoose" resultType="entity.User" parameterType="entity.User">
select * from user
<where>
<choose>
<!-- 条件1:用户名不为空 → 按用户名查 -->
<when test="username!=null">
username = #{username}
</when>
<!-- 条件2:地址不为空 → 按地址查 -->
<when test="address !=null">
address = #{address}
</when>
<!-- 都不满足 → 按ID查 -->
<otherwise>
id = #{id}
</otherwise>
</choose>
</where>
</select>
2. 测试效果
即使同时传了username、address、id,也仅执行第一个满足的分支 (这里是username):
sql
select * from user WHERE username=?
Parameters: 小王(String)


五、批量操作:<foreach>(补充数组入参处理)
动态 SQL 的<foreach>标签支持批量删除、批量插入等场景,当入参是数组时,需通过@Param注解指定参数名。
1. 批量删除(id in(...)):数组入参的@Param注解
当批量删除的入参是数组时,MyBatis 默认无法明确识别参数名,需通过@Param强制指定。
(1)Dao 层接口(指定参数名)
java
运行
// @Param("ids"):将数组参数名指定为"ids",方便Mapper.xml引用
public int deleteMore(@Param("ids") Integer[] ids);
(2)Mapper.xml 配置(关联参数名)
xml
<delete id="deleteMore">
delete from user where id in
<!-- collection="ids":对应@Param注解的参数名 -->
<!-- item="id":遍历数组时,每个元素的别名 -->
<!-- separator=",":元素之间的分隔符 -->
<!-- open="("、close=")":遍历结果的前后缀 -->
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
(3)测试代码与执行效果
java
运行
@Test
public void deleteMoreTest(){
Integer[] ids=new Integer[]{4,5,6}; // 要删除的id数组
int cnt = mapper.deleteMore(ids);
System.out.println(cnt); // 输出影响行数:3
}
最终执行的 SQL(自动拼接数组元素):
sql
delete from user where id in ( ?, ?, ? )
Parameters: 4(Integer), 5(Integer), 6(Integer)



2. 批量插入(values(...))
xml
<insert id="batchInsert">
insert into user(username,birthday,sex,address)
values
<foreach collection="list" item="user" separator=",">
(#{user.username},#{user.birthday},#{user.sex},#{user.address})
</foreach>
</insert>
六、附加注意:实体类构造函数的 "坑"
在 MyBatis 操作(如插入、查询)中,实体类的构造函数配置不当会导致实例化失败,常见问题:
1. 问题场景
若自定义了带参构造函数 (不含id),但未保留无参构造函数,MyBatis 无法通过反射实例化对象,会出现报错:
java
运行
// 自定义带参构造函数(不含id),但缺失无参构造
public User(String username, String sex, Date birthday, String address) {
this.username = username;
this.sex = sex;
this.birthday = birthday;
this.address = address;
}
2. 解决方法
- 必须保留无参构造函数:MyBatis 反射实例化对象依赖无参构造;
- 构造函数字段匹配 :若
id是自增字段(插入时无需传值),构造函数可不含id,但要确保实体类的setter方法完整(MyBatis 会通过setter赋值)。
总结
MyBatis 动态 SQL 通过<if>、<where>、<set>、<trim>、<choose>、<foreach>等标签实现了 SQL 的灵活拼接,结合@Param注解可解决数组 / 集合入参问题,同时需注意实体类构造函数的规范。核心应用场景:
- 多条件查询:
<where>+<if> - 部分字段更新:
<set>+<if> - 复杂拼接:
<trim> - 分支选择:
<choose>+<when>+<otherwise> - 批量操作:
<foreach>(配合@Param处理数组)
