MyBatis动态SQL实战笔记:从基础标签到批量操作
前言
在MyBatis开发中,手动拼接SQL语句不仅繁琐,还容易出现语法错误(比如多余的and、逗号,或者缺少where关键字)。动态SQL是MyBatis的核心特性之一,能根据不同条件动态生成SQL语句,完美解决上述痛点。本文结合课堂练习的User模块代码,梳理MyBatis动态SQL核心标签的使用方法和场景。
一、环境准备
1. 核心配置文件(SqlMapConfig.xml)
配置MyBatis的基础环境,包括数据源、日志、映射器扫描:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 开启日志输出,方便调试SQL -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<!-- 配置MySQL环境 -->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 扫描Mapper映射文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
2. 实体类(User.java)
对应数据库user表,包含id、username、birthday、sex、address字段,提供无参/有参构造、get/set方法、toString方法:
java
package com.qcby.entity;
import java.util.Date;
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// 构造方法、get/set、toString 略
}
3. DAO接口(UserDao.java)
定义数据库操作方法,与Mapper.xml中的SQL标签ID一一对应:
java
package com.qcby.dao;
import com.qcby.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserDao {
List<User> findUser(User user); // where+if查询
int updateUser(User user); // set+if更新
List<User> findUserTrim(User user); // trim查询
int updateUserTrim(User user); // trim更新
List<User> findUserChoose(User user); // choose分支查询
int deleteMore(@Param("ids") Integer[] ids); // 批量删除
int insertMore(@Param("users") List<User> users); // 批量插入
}
二、动态SQL核心标签解析
1. where + if:多条件灵活查询
作用
if:根据条件判断是否拼接SQL片段;where:自动包裹满足条件的if片段,且自动剔除片段开头多余的and/or,若没有满足条件的片段,自动省略where关键字。
代码示例(UserMapper.xml)
xml
<select id="findUser" parameterType="com.qcby.entity.User" resultType="com.qcby.entity.User">
select * from user
<where>
<if test="username != null">
username like concat('%', #{username}, '%')
</if>
<if test="sex != null">
and sex = #{sex}
</if>
<if test="address != null">
and address like concat('%', #{address}, '%')
</if>
<if test="birthday != null">
and birthday = #{birthday}
</if>
</where>
</select>
关键注意
test属性:判断条件,使用OGNL表达式(直接引用实体类属性名);- 模糊查询:MySQL中用
concat('%', #{字段}, '%')拼接通配符。
2. set + if:动态更新字段
作用
set:自动包裹满足条件的更新片段,剔除片段末尾多余的逗号,最终拼接set关键字。
代码示例
xml
<update id="updateUser" parameterType="com.qcby.entity.User">
update user
<set>
<if test="username != null">
username = #{username},
</if>
<if test="sex != null">
sex = #{sex},
</if>
<if test="address != null">
address = #{address},
</if>
<if test="birthday != null">
birthday = #{birthday},
</if>
</set>
where id = #{id}
</update>
3. trim:万能替换标签
作用
trim可替代where和set,通过属性自定义拼接规则,更灵活:
prefix:给拼接后的片段加前缀(如where/set);prefixOverrides:剔除片段开头的指定字符(如and|or);suffixOverrides:剔除片段末尾的指定字符(如,);suffix:给拼接后的片段加后缀(较少用)。
替代where + if示例
xml
<select id="findUserTrim" parameterType="com.qcby.entity.User" resultType="com.qcby.entity.User">
select * from user
<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 like concat('%', #{address}, '%')
</if>
<if test="birthday != null">
and birthday = #{birthday}
</if>
</trim>
</select>
替代set + if示例
xml
<update id="updateUserTrim" parameterType="com.qcby.entity.User">
update user
<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>
<if test="birthday != null">
birthday = #{birthday},
</if>
</trim>
where id = #{id}
</update>
4. choose/when/otherwise:分支选择查询
作用
类似Java的if-else if-else,只执行第一个满足条件的when ,无满足条件则执行otherwise。
业务场景
用户输入用户名则按用户名查,输入地址则按地址查,否则按ID查:
xml
<select id="findUserChoose" parameterType="com.qcby.entity.User" resultType="com.qcby.entity.User">
select * from user
<where>
<choose>
<when test="username != null">
username like concat('%', #{username}, '%')
</when>
<when test="address != null">
address like concat('%', #{address}, '%')
</when>
<otherwise>
id = #{id}
</otherwise>
</choose>
</where>
</select>
5. foreach:批量操作核心
作用
遍历集合/数组,常用于批量删除、批量插入。
核心属性
collection:指定要遍历的集合/数组(DAO接口需用@Param指定别名);item:遍历的每个元素的别名;open:遍历结果的开头字符(如();close:遍历结果的结尾字符(如));separator:遍历元素之间的分隔符(如,)。
场景1:批量删除
xml
<delete id="deleteMore" >
delete from user where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
DAO接口:int deleteMore(@Param("ids") Integer[] ids);
场景2:批量插入
xml
<insert id="insertMore" parameterType="java.util.List">
insert into user(username, birthday, sex, address)
values
<foreach collection="users" item="user" separator=",">
(#{user.username}, #{user.birthday}, #{user.sex}, #{user.address})
</foreach>
</insert>
DAO接口:int insertMore(@Param("users") List<User> users);
三、核心总结
| 标签组合 | 适用场景 | 核心优势 |
|---|---|---|
where + if |
多条件查询 | 自动处理where和多余and/or |
set + if |
动态更新字段 | 自动处理set和多余逗号 |
trim |
自定义拼接规则 | 替代where/set,灵活性更高 |
choose/when/otherwise |
分支选择查询(互斥条件) | 只执行一个分支,避免多条件冲突 |
foreach |
批量删除、批量插入 | 高效遍历集合,拼接批量SQL |
动态SQL的核心价值是减少硬编码 、避免SQL语法错误 、适配多条件业务场景 。实际开发中,需根据业务需求选择合适的标签:简单多条件查询用where + if,复杂拼接用trim,互斥条件用choose,批量操作必用foreach。
拓展思考
- 模糊查询除了
concat,还可使用${}(但有SQL注入风险,慎用); foreach遍历List和数组时,collection属性值需与@Param别名一致;- 可将重复的SQL片段抽取到
<sql>标签中,通过<include>引用,减少冗余。