MyBatis动态SQL

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&amp;serverTimezone=UTC&amp;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可替代whereset,通过属性自定义拼接规则,更灵活:

  • 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

拓展思考

  1. 模糊查询除了concat,还可使用${}(但有SQL注入风险,慎用);
  2. foreach遍历List和数组时,collection属性值需与@Param别名一致;
  3. 可将重复的SQL片段抽取到<sql>标签中,通过<include>引用,减少冗余。
相关推荐
MandalaO_O3 小时前
MyBatis 与 MySQL 执行流程
数据库·mysql·mybatis
l1t4 小时前
DeepSeek总结的将 Rust Delta Kernel 集成到 ClickHouse
数据库·clickhouse·rust
qq_283720055 小时前
万字深度:Chroma 向量数据库全解析 — 核心原理、实战操作、性能优化与工程最佳实践
数据库·性能优化
黄筱筱筱筱筱筱筱5 小时前
二进制包安装MySql服务
数据库
初心未改HD5 小时前
LLM应用开发之向量数据库详解
数据库·人工智能
键盘上的猫头鹰5 小时前
【从零学MySQL(三)】数据增删改(DML)及 SELECT 查询详解
数据库·mysql·数据分析
KaMeidebaby6 小时前
卡梅德生物技术快报|蛋白的过表达质粒构建与生信分析实验全流程复盘
前端·数据库·其他·百度·新浪微博
渣渣盟6 小时前
数据库之两段锁协议相关理论及应用
数据库·关系规范化·两段锁协议
LCG元6 小时前
Istio - 服务网格流量治理深度解析:灰度发布 / 故障注入配置实践
java·数据库·istio