一文详解MyBatis中动态SQL的封装原理与常用标签实战应用

 更新时间:2026年04月08日 09:02:15   作者:身如柳絮随风扬  
这篇文章主要为大家详细介绍了MyBatis中动态SQL的核心技术与及常用标签的应用,同时提供了最佳实践和常见问题解决方案,感兴趣的小伙伴可以跟随小编一起学习一下

告别繁琐的SQL拼接,让动态查询像搭积木一样简单

一、为什么需要动态SQL?

在实际开发中,我们经常会遇到需要根据不同的条件拼接SQL语句的场景。传统的做法是使用Java代码进行字符串拼接,但这种方式不仅代码冗余、易出错,还存在SQL注入的风险。MyBatis提供的动态SQL机制,允许我们在XML映射文件中使用标签来构建灵活的SQL语句,让代码更清晰、更安全。

核心优势:

  • 无需手动处理多余的空格、AND/OR、逗号分隔符
  • 基于OGNL表达式,与Java对象无缝集成
  • 提升可维护性,SQL结构一目了然

二、动态SQL的执行流程

MyBatis在解析动态SQL时,会根据传入的参数动态生成最终的SQL语句。下图展示了从XML到预编译SQL的完整过程:

三、常用动态SQL标签详解

1.<if>– 条件判断

最简单的条件标签,当条件满足时拼接SQL片段。

语法:

<if test="条件表达式">
    SQL片段
</if>

示例: 根据姓名和年龄动态查询用户

<select id="findUsers" resultType="User">
    SELECT * FROM user WHERE 1=1
    <if test="name != null and name != ''">
        AND name = #{name}
    </if>
    <if test="age != null and age > 0">
        AND age = #{age}
    </if>
</select>

注意: WHERE 1=1 是为了防止所有条件都不满足时出现语法错误。更好的替代方案是使用 <where> 标签。

2.<where>– 智能处理查询条件

自动处理AND/OR关键字,并去除多余的连接词。

示例: 重构上面的查询

<select id="findUsers" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null and name != ''">
            AND name = #{name}
        </if>
        <if test="age != null and age > 0">
            AND age = #{age}
        </if>
    </where>
</select>

name 有值而 agenull 时,生成的SQL为:

  • SELECT * FROM user WHERE name = ?
  • <where> 标签会自动去掉开头的 AND

3.<set>– 动态更新字段

<update> 语句中智能处理逗号分隔符。

示例: 动态更新用户信息

<update id="updateUser">
    UPDATE user
    <set>
        <if test="name != null">name = #{name},</if>
        <if test="age != null">age = #{age},</if>
        <if test="email != null">email = #{email},</if>
    </set>
    WHERE id = #{id}
</update>

<set> 会自动去掉末尾多余的逗号,并保证至少有一个字段被更新。

4.<foreach>– 遍历集合/数组

常用于 IN 查询、批量插入、批量删除。

属性说明:

属性作用
collection要遍历的集合/数组名称
item每次迭代的当前元素别名
index当前元素的索引(或map的key)
open遍历开始拼接的字符串
close遍历结束拼接的字符串
separator元素之间的分隔符

示例1: 批量查询(IN条件)

<select id="findByIds" resultType="User">
    SELECT * FROM user WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

示例2: 批量插入

<insert id="batchInsert">
    INSERT INTO user (name, age) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.age})
    </foreach>
</insert>

常见坑点: collection 默认支持 listarraymap。如果是自定义参数名,需要使用 @Param 注解指定。

5.<trim>– 自定义前缀/后缀处理

<where><set> 本质上都是 <trim> 的快捷方式,当需要更精细的控制时可直接使用 <trim>

属性:

  • prefix – 整体前添加的字符串
  • suffix – 整体后添加的字符串
  • prefixOverrides – 去除前缀中的指定字符串
  • suffixOverrides – 去除后缀中的指定字符串

等价关系:

<!-- <where> 等价于 -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
    ...
</trim>

<!-- <set> 等价于 -->
<trim prefix="SET" suffixOverrides=",">
    ...
</trim>

自定义示例: 动态添加 ORDER BY 子句

<select id="selectWithOrder" resultType="User">
    SELECT * FROM user
    <trim prefix="ORDER BY" suffixOverrides=",">
        <if test="orderByName">name,</if>
        <if test="orderByAge">age,</if>
    </trim>
</select>

6.<choose> / <when> / <otherwise>– 分支选择

类似Java的 switch-case,只执行第一个满足条件的分支。

示例: 按优先级排序查询(姓名优先,否则年龄,否则默认)

<select id="findUsersWithPriority" resultType="User">
    SELECT * FROM user
    <where>
        <choose>
            <when test="name != null">
                AND name LIKE CONCAT('%', #{name}, '%')
            </when>
            <when test="age != null">
                AND age = #{age}
            </when>
            <otherwise>
                AND status = 'ACTIVE'
            </otherwise>
        </choose>
    </where>
</select>

四、综合实战:员工管理系统动态查询

下面是一个结合多个标签的完整示例:

<mapper namespace="com.example.EmployeeMapper">
    
    <!-- 动态查询员工 -->
    <select id="searchEmployees" resultType="Employee">
        SELECT * FROM employee
        <where>
            <if test="deptId != null">
                AND dept_id = #{deptId}
            </if>
            <if test="minSalary != null">
                AND salary >= #{minSalary}
            </if>
            <if test="maxSalary != null">
                AND salary &lt;= #{maxSalary}
            </if>
            <if test="keywords != null and keywords != ''">
                AND (name LIKE CONCAT('%', #MyBatis动态SQL封装,MyBatis动态SQL使用,MyBatis动态SQL,MyBatis SQL, '%')
                     OR position LIKE CONCAT('%', #MyBatis动态SQL封装,MyBatis动态SQL使用,MyBatis动态SQL,MyBatis SQL, '%'))
            </if>
            <choose>
                <when test="hireDateStart != null and hireDateEnd != null">
                    AND hire_date BETWEEN #{hireDateStart} AND #{hireDateEnd}
                </when>
                <when test="hireDateStart != null">
                    AND hire_date >= #{hireDateStart}
                </when>
                <when test="hireDateEnd != null">
                    AND hire_date &lt;= #{hireDateEnd}
                </when>
            </choose>
        </where>
        <if test="orderBy != null">
            <trim prefix="ORDER BY" suffixOverrides=",">
                <if test="orderBy.name">name,</if>
                <if test="orderBy.salary">salary,</if>
                <if test="orderBy.hireDate">hire_date,</if>
            </trim>
        </if>
    </select>
    
</mapper>

五、最佳实践与常见问题

推荐做法

  • 优先使用 <where>/<set> 而非手写 1=1SET 后跟逗号。
  • 结合 <trim> 处理复杂的动态前缀/后缀逻辑。
  • 使用 @Param 为集合参数命名,避免依赖默认的 list/array
  • 对字符串判空test="name != null and name != ''"

常见错误

错误现象原因解决方案
动态SQL不生效忘记在<select>中嵌入动态标签检查标签是否正确嵌套
多余的AND/OR条件未全部满足时前缀残留使用<where><trim prefixOverrides>
批量插入性能差foreach生成超大SQL改用分批处理或JDBC batch
OGNL表达式异常使用了Java的&&/`

六、总结

MyBatis的动态SQL通过一套简洁的XML标签,完美解决了传统JDBC拼接SQL的痛点。掌握以下核心标签即可应对90%的动态场景:

标签核心用途
<if>简单条件判断
<where>智能查询条件组装
<set>智能更新字段组装
<foreach>遍历集合(IN、批量操作)
<trim>自定义前缀/后缀处理
<choose>多分支互斥选择

动态SQL的本质是在XML层面基于OGNL表达式进行零逻辑的SQL片段组合。理解这一思想后,你可以灵活组合这些标签,写出既优雅又安全的动态SQL。

延伸思考: 如果动态条件非常复杂(超过5个分支),建议考虑将部分逻辑迁移到Java层,使用@SelectProvider注解或构建查询对象模式,以保持XML的可读性。

到此这篇关于一文详解MyBatis中动态SQL的封装原理与常用标签实战应用的文章就介绍到这了,更多相关MyBatis动态SQL应用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Mybatis-plus基于redis实现二级缓存过程解析

    Mybatis-plus基于redis实现二级缓存过程解析

    这篇文章主要介绍了Mybatis-plus基于redis实现二级缓存过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • java如何根据模板导出数据到word文档中(表格、自定义标签等)

    java如何根据模板导出数据到word文档中(表格、自定义标签等)

    这篇文章主要介绍了关于java如何根据模板导出数据到word文档中(表格、自定义标签等)的相关资料,主要包括创建docx文档,配置模板信息,以及利用XDocReport+FreeMarker技术进行实现,详细介绍了在Word模板中如何设置字段以及如何通过代码填充这些字段,需要的朋友可以参考下
    2024-11-11
  • 基于Springboot疫苗接种行程管理系统的设计与实现

    基于Springboot疫苗接种行程管理系统的设计与实现

    本文主要介绍了基于Springboot实现的疫苗接种行程管理系统的示例代码,系统主要实现个人疫苗接种管理、行程管理、病史管理、风险地区管理、核酸检测报告结果上报、疫情新闻管理等功能,需要的可以参考一下
    2022-03-03
  • 详解SpringBoot Schedule配置

    详解SpringBoot Schedule配置

    本篇文章主要介绍了详解SpringBoot Schedule配置 ,可以实现定时任务,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • Spring和SpringBoot比较及区别解惑

    Spring和SpringBoot比较及区别解惑

    这篇文章主要介绍了Spring和SpringBoot比较解惑区别,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • SpringBoot结合WebSocket实现聊天功能

    SpringBoot结合WebSocket实现聊天功能

    本文介绍了如何使用SpringBoot和WebSocket实现一个简单的聊天功能,包括导入依赖、配置类、创建消息实体、指定ServerEndpoint、创建客户端等步骤,通过具体示例,演示了如何发送个人消息和群发消息,实现了基本的聊天功能,适合需要在项目中实现实时通讯功能的开发者参考
    2024-11-11
  • springboot aop配合反射统一签名验证实践

    springboot aop配合反射统一签名验证实践

    这篇文章主要介绍了springboot aop配合反射统一签名验证实践,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java中Builder模式的实现详解

    Java中Builder模式的实现详解

    在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成。下面这篇文章主要给大家介绍了在Java各个版本中Builder模式实现的相关资料,文中介绍的非常详细,需要的朋友可以参考学习。
    2017-05-05
  • Java中的内存分配图解

    Java中的内存分配图解

    这篇文章主要介绍了Java中的内存分配图解,Java 程序运行时,需要在内存中分配空间。为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式,需要的朋友可以参考下
    2023-08-08
  • 详解SpringBoot读取Yml配置文件的3种方法

    详解SpringBoot读取Yml配置文件的3种方法

    本文主要介绍了详解SpringBoot读取Yml配置文件的3种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04

最新评论