MyBatis:动态 SQL 全景梳理

在实际业务开发中,数据库查询条件几乎不会一成不变:前端多条件筛选、非必填参数、动态排序、分页拼接、批量增删改等场景,固定写死的SQL语句完全无法适配业务需求。

MyBatis 动态SQL 是其核心核心能力之一,区别于静态SQL,可根据参数是否为空、参数值、业务状态自动拼接、裁剪、优化SQL语句,彻底解决多条件动态查询、批量操作、条件分支适配问题。

本文将从核心原理、全套标签详解、场景Demo、对比表格、实战规范、常见坑点全方位梳理,覆盖99%企业开发动态SQL场景。

一、动态SQL核心原理与优势

1.1 核心原理

MyBatis 在执行SQL前,会通过OGNL表达式解析XML中的动态标签,根据传入的参数对象属性值,动态判断是否拼接SQL片段,最终生成一条合规、无语法错误的可执行SQL,再交由数据库执行。

1.2 动态SQL vs 静态SQL 对比

对比维度 静态SQL 动态SQL
写法特点 SQL语句固定写死,无逻辑判断 通过标签+表达式动态拼接SQL片段
参数适配性 参数缺失会报SQL语法错误、查询异常 自动忽略空参数,适配非必填条件
业务场景 仅适用于固定条件查询、简单CRUD 多条件筛选、批量操作、动态排序/分页
维护成本 多场景需写多条SQL,冗余极高 一条SQL适配全场景,统一维护
语法安全性 易出现多余and/or、逗号等语法问题 内置语法裁剪机制,杜绝语法错误

1.3 动态SQL全套核心标签总览

标签 核心作用 适用场景
if 单条件判断,满足条件则拼接SQL 非必填查询条件、参数动态拼接
where 自动去除多余的 and/or,智能拼接where关键字 多if条件组合查询,解决语法冗余问题
trim 自定义前后缀、去除指定冗余字符,高度灵活 where/set无法满足的特殊裁剪场景
set 动态拼接update字段,自动去除末尾多余逗号 动态更新字段(部分字段更新)
choose/when/otherwise 多条件互斥分支,只执行一个匹配条件 单选条件筛选(优先级判断)
foreach 遍历集合、数组,批量拼接SQL片段 批量查询、批量新增、批量删除
bind 绑定自定义变量,简化表达式、防止SQL注入 模糊查询、复杂参数处理
include/sql 抽取通用SQL片段,复用代码 公共字段、通用查询条件复用

二、全标签深度解析 + 可直接运行Demo

统一前置实体与参数:本文所有Demo基于 User 实体类user 数据表,适配SpringBoot + MyBatis 常规项目。

复制代码
// User实体类
public class User {
    private Long id;
    private String username;
    private Integer age;
    private String phone;
    private Integer status; // 状态 0-禁用 1-正常
    private LocalDateTime createTime;
    // getter/setter 省略
}

// 前端查询参数DTO(动态查询入参)
public class UserQuery {
    private String username; // 模糊查询
    private Integer age; // 精准查询
    private Integer status; // 状态筛选
    private List<Long> idList; // 批量ID
    // getter/setter 省略
}

2.1 if 标签:基础单条件动态拼接

作用:判断参数是否非空,满足条件则拼接对应SQL,最基础、使用频率最高的动态标签。

语法规则:test属性为OGNL表达式,支持非空判断、数值判断、字符串判断。

复制代码
<!-- 多条件动态查询用户 -->
<select id="listUserByCondition" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
    SELECT id, username, age, phone, status, create_time
    FROM user
    WHERE del_flag = 0
    <!-- 用户名非空则模糊查询 -->
    <if test="username != null and username != ''">
        AND username LIKE CONCAT('%', #{username}, '%')
    </if>
    <!-- 年龄不为空则精准查询 -->
    <if test="age != null">
        AND age = #{age}
    </if>
    <!-- 状态不为空则筛选 -->
    <if test="status != null">
        AND status = #{status}
    </if>
</select>

坑点注意 :单纯使用if标签,首个条件为空时,会残留多余的 AND,导致SQL语法报错,需配合 where标签 使用。

2.2 where 标签:智能处理查询条件前缀

核心优势 :1. 自动识别并添加where关键字;2. 自动去除第一个条件前多余的 and/or;3. 无任何条件时,不生成where语句。

完美解决if标签单独使用的语法报错问题,企业开发查询场景必用

复制代码
<select id="listUserByCondition" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
    SELECT id, username, age, phone, status, create_time
    FROM user
    <where>
        del_flag = 0
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
</select>

2.3 trim 标签:自定义语法裁剪(万能替代where/set)

作用:自定义拼接前缀、后缀,同时裁剪指定的首尾多余字符,是动态SQL的万能语法修复标签。

核心属性

  • prefix:整体拼接的前缀字符串

  • suffix:整体拼接的后缀字符串

  • prefixOverrides:需要去除的首部字符(and/or)

  • suffixOverrides:需要去除的尾部字符(逗号)

Demo:trim实现where标签效果

复制代码
<select id="listUserByTrim" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
    SELECT id, username, age, phone, status, create_time
    FROM user
    <trim prefix="WHERE" prefixOverrides="AND|OR">
        del_flag = 0
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </trim>
</select>

2.4 set 标签:动态更新字段专用

场景:后台修改用户信息时,只更新传入的字段,空字段不更新,避免覆盖原有数据。

核心优势 :自动去除最后一个字段后的多余逗号,彻底解决动态更新的语法报错问题。

复制代码
<update id="updateUserDynamic" parameterType="com.xxx.entity.User">
    UPDATE user
    <set>
        <if test="username != null and username != ''">
            username = #{username},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
        <if test="phone != null and phone != ''">
            phone = #{phone},
        </if>
        <if test="status != null">
            status = #{status}
        </if>
    </set>
    WHERE id = #{id} AND del_flag = 0
</update>

2.5 choose/when/otherwise:互斥分支判断

核心特点 :多条件单选互斥 ,自上而下匹配,匹配成功一个分支后,不再执行后续分支,类似 Java 的 if-else if-else

适用场景:优先级筛选、唯一条件匹配(如:优先ID查询,无ID则用户名查询,最后查全部)

复制代码
<select id="getUserByPriority" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
    SELECT id, username, age, status
    FROM user
    <where>
        del_flag = 0
        <choose>
            <!-- 优先根据ID精准查询 -->
            <when test="id != null">
                AND id = #{id}
            </when>
            <!-- ID为空则根据用户名查询 -->
            <when test="username != null and username != ''">
                AND username = #{username}
            </when>
            <!-- 所有条件为空,默认查询正常状态用户 -->
            <otherwise>
                AND status = 1
            </otherwise>
        </choose>
    </where>
</select>

2.6 foreach 标签:批量操作核心(高频必考)

核心场景:批量删除、批量新增、IN集合查询、批量更新。

核心属性说明

属性 作用
collection 遍历集合/数组参数名(List填list、数组填array、自定义参数填参数名)
item 遍历后的单个元素别名
index 遍历下标/Map键名
open 遍历整体前缀(如 IN ( 的左括号)
close 遍历整体后缀(如 ) 的右括号)
separator 元素之间的分隔符(逗号、OR等)

Demo1:IN 批量查询(根据ID集合查询用户)

复制代码
<select id="listUserByIdList" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
    SELECT * FROM user WHERE del_flag = 0
    <if test="idList != null and idList.size() > 0">
        AND id IN
        <foreach collection="idList" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </if>
</select>

Demo2:批量删除用户

复制代码
<delete id="batchDeleteUser" parameterType="java.util.List">
    DELETE FROM user WHERE id IN
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</delete>

Demo3:批量新增用户(高性能)

复制代码
<insert id="batchInsertUser" parameterType="java.util.List">
    INSERT INTO user (username, age, phone, status, create_time)
    VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.username}, #{user.age}, #{user.phone}, #{user.status}, NOW())
    </foreach>
</insert>

2.7 bind 标签:参数绑定与模糊查询优化

作用 :自定义绑定变量,简化OGNL表达式,解决数据库模糊查询兼容性问题(MySQL、Oracle适配),同时预防SQL注入。

复制代码
<select id="listUserByLike" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
    <!-- 绑定模糊查询变量,全局复用 -->
    <bind name="likeUsername" value="'%' + username + '%'"/>
    SELECT * FROM user
    <where>
        del_flag = 0
        <if test="username != null and username != ''">
            AND username LIKE #{likeUsername}
        </if>
    </where>
</select>

2.8 sql + include:SQL片段复用

作用:抽取通用SQL片段(公共字段、通用查询条件),避免代码冗余,统一维护。

复制代码
<!-- 抽取公共查询字段 -->
<sql id="userCommonField">
    id, username, age, phone, status, create_time
</sql>

<!-- 抽取通用删除条件 -->
<sql id="delFlagCondition">
    del_flag = 0
</sql>

<select id="listAllUser" resultType="com.xxx.entity.User">
    SELECT <include refid="userCommonField"/>
    FROM user
    <where>
        <include refid="delFlagCondition"/>
    </where>
</select>

三、动态SQL高频场景整合Demo

3.1 综合多条件筛选(if+where+bind)

适配:用户名模糊、年龄区间、状态筛选,全非必填参数

复制代码
<select id="listUserComplexQuery" resultType="com.xxx.entity.User" parameterType="com.xxx.dto.UserQuery">
    <bind name="likeName" value="'%' + username + '%'"/>
    SELECT id, username, age, status, create_time
    FROM user
    <where>
        del_flag = 0
        <if test="username != null and username != ''">
            AND username LIKE #{likeName}
        </if>
        <if test="minAge != null">
            AND age >= #{minAge}
        </if>
        <if test="maxAge != null">
            AND age <= #{maxAge}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

3.2 动态字段更新(set+if)

只更新传入的非空字段,保留数据库原有旧数据

复制代码
<update id="updateUserSelective" parameterType="com.xxx.entity.User">
    UPDATE user
    <set>
        <if test="username != null and username != ''">username=#{username},</if>
        <if test="age != null">age=#{age},</if>
        <if test="phone != null and phone != ''">phone=#{phone},</if>
        <if test="status != null">status=#{status},</if>
        update_time = NOW()
    </set>
    WHERE id = #{id} AND del_flag = 0
</update>

四、动态SQL核心避坑指南(高频报错)

常见坑点 报错原因 解决方案
多余AND/OR语法错误 首个if条件为空,残留前置AND 所有多条件查询统一使用 <where> 标签
UPDATE末尾多余逗号 动态字段最后一条拼接逗号 更新语句统一使用 <set> 标签
foreach空集合报错 集合为空时生成 IN() 空语法 遍历前加判断:size>0
字符串空串判断遗漏 只判断null,未判断空字符串,导致无效查询 字符串统一判断:str != null and str != ''
choose多分支同时生效 误用if替代choose,未理解互斥逻辑 单选条件必须使用choose,不可用多个if

五、企业开发动态SQL规范总结

  1. 查询场景 :多条件非必填查询,固定搭配where + if,杜绝手写where关键字

  2. 更新场景 :局部动态更新,必须使用 set + if,防止逗号语法错误

  3. 单选分支 :优先级、互斥条件,强制使用 choose/when/otherwise

  4. 批量操作 :所有集合遍历必须用 foreach,且前置非空判断

  5. 代码复用 :公共字段、通用条件统一抽取 sql片段,全局include引用

  6. 参数判断:数值型只判null,字符串必须同时判null和空串

六、全文总结

MyBatis动态SQL的核心价值是适配业务不确定性,通过8大核心标签,可完美解决:多条件筛选、局部更新、批量操作、分支查询等所有复杂业务场景。

开发核心口诀:查询用where、更新用set、单选用choose、批量用foreach、复用抽sql、空参必判断

掌握本文所有Demo与避坑要点,可完全覆盖企业开发中99%的动态SQL开发场景,杜绝语法报错与代码冗余问题。

相关推荐
360智汇云17 小时前
KV-Probe:通用 KV 数据库测试套件
数据库
x8617 小时前
我与 IT 这三十年:2015,大数据平台的重与轻
数据库·it史
z落落17 小时前
T-SQL 事务(Transaction)
java·数据库·sql
就改了18 小时前
Mybatis快速入门大全(详细版)
java·spring boot·mybatis
ClouGence19 小时前
MySQL迁移到达梦怎么做?信创数据库低停机迁移实战
数据库·sql·mysql
星空露珠19 小时前
迷你世界3.0API,事件监听
开发语言·数据结构·数据库·游戏·lua
数据库安全19 小时前
灾备演练双月报|美创 DRCC 筑牢红十字医院医疗系统安全底线
数据库·安全
IvorySQL19 小时前
PG 日报|PG20 正式计划移除 refint 模块,官方指引迁移原生外键
数据库·人工智能·postgresql·开源·区块链
小罗水19 小时前
第11章 PostgreSQL + pgvector 向量检索
java·数据库·spring cloud·微服务