MyBatis 动态 SQL 详解:从基础到进阶实战

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(自动拼接whereand):

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处理数组)
相关推荐
老邓计算机毕设2 小时前
SSM校园订餐系统7z0dm(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·菜品管理系统·ssm 框架·ssm 框架开发·校园线上订餐平台
sxlishaobin2 小时前
MySQL- explain
数据库·mysql
软件管理系统2 小时前
基于Spring Boot的便民维修管理系统
java·spring boot·后端
曹牧3 小时前
Oracle:判断一个字符串出现次数
数据库·oracle
源代码•宸3 小时前
Leetcode—620. 有趣的电影&&Q3. 有趣的电影【简单】
数据库·后端·mysql·算法·leetcode·职场和发展
百***78753 小时前
Step-Audio-2 轻量化接入全流程详解
android·java·gpt·php·llama
快乐肚皮3 小时前
MySQL递归CTE
java·数据库·mysql·递归表达式
廋到被风吹走3 小时前
【Spring】DispatcherServlet解析
java·后端·spring