有关MyBatis的动态SQL

有关MyBatis动态SQL

MyBatis动态SQL是一种根据不同条件灵活拼接SQL语句的技术,基于OGNL表达式实现。动态 SQL 大大减少了编写代码的工作量,更体现了 MyBatis 的灵活性、高度可配置性和可维护性。

1.什么是动态SQL?

动态sql可以在一些需要灵活拼接sql语句的场景中使用,作用是根据一些条件,拼接出需要的sql语句。

动态 SQL 是 MyBatis 的强大特性之一。在 JDBC 或其它类似的框架中,开发人员通常需要手动拼接 SQL 语句。根据不同的条件拼接 SQL 语句是一件极其痛苦的工作。而动态 SQL 恰好解决了这一问题,可以根据场景动态的构建查询。

动态 SQL 只有几个基本元素,与 JSTL 或 XML 文本处理器相似,十分简单明了,大量的判断都可以在 MyBatis 的映射 XML 文件里配置,以达到许多需要大量代码才能实现的功能。

MyBatis 也可以在注解中配置 SQL,但是由于注解功能受限,且对于复杂的 SQL 语句来说可读性差,所以使用较少。本教程不对它们进行介绍。

2.动态SQL的作用与原理

2.1 作用

  • 灵活拼接SQL:根据参数值动态添加或删除SQL片段,例如条件查询、批量操作、多分支选择等场景
  • 简化代码:避免手动处理逗号、空格、WHERE/AND/OR等语法细节,提升代码可读性
  • 提高安全性 :通过预编译机制(#{})防止SQL注入,但需注意复杂逻辑下的潜在风险

2.2 执行原理

  • OGNL表达式:MyBatis使用OGNL(Object-Graph Navigation Language)从参数对象中解析条件值,判断是否包含特定SQL片段

  • 动态解析流程

    ① XML中的SQL标签(如<if> <where>、)被解析为SqlNode对象;

    ② 运行时根据参数值生成BoundSql对象,拼接最终SQL;

    ③ 核心类如OgnlExpressionEvaluator处理表达式计算,TrimSqlNodeWhereSqlNode

    等处理语法修剪;

3.常用动态SQL标签及用法

MyBatis 的动态 SQL 包括以下几种元素,如下表所示。

元素 作用 备注
if 判断语句 单条件分支判断
choose(when、otherwise) 相当于 Java 中的 switch case 语句 多条件分支判断
trim、where 辅助元素 用于处理一些SQL拼装问题
foreach 循环语句 在in语句等列举条件常用
bind 辅助元素 拼接参数

3.1 <if>标签

  • 功能 :通过test属性判断条件是否成立,决定是否包含SQL片段。当判断条件为 true 时,才会执行所包含的 SQL 语句

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

    xml 复制代码
    <select id="selectUser">
        SELECT * FROM user
        <where>
            <if test="name != null">AND name = #{name}</if>
            <if test="age > 0">AND age = #{age}</if>
        </where>
    </select>
    • 示例:可多个 if 语句同时使用。以下语句表示为可以按照student名称(stu_name)或者stu_age进行模糊查询。如果您不输入名称或年龄,则返回所有的student记录。但是,如果你传递了任意一个参数,它就会返回与给定参数相匹配的记录。
    xml 复制代码
    <select id="select13" resultType="Student">
            select * from student where true
            <if test="name!=null">
                and stu_name='${name}'
            </if>
            <if test="age!=null">
                and stu_age=${age}
            </if>
        </select>

    注意:字符串比较需用toString()避免类型错误,如

    xml 复制代码
    test="sex == '1'.toString()"

3.2 <where>标签

  • 功能 :自动生成WHERE子句,并去除首个条件的AND/OR前缀,避免语法错误。

  • if 语句中判断条件为 true 时,where 关键字才会加入到组装的 SQL 里面,否则就不加入。where 会检索语句,它会将 where 后的第一个 SQL 条件语句的 AND 或者 OR 关键词去掉。

  • 示例:

    xml 复制代码
    <where>
        <if test="name != null">name = #{name}</if>
        <if test="age != null">AND age = #{age}</if>
    </where>
    • 最佳实践:每个条件建议以AND/OR开头,<where>会自动处理首前缀,where后面的意思是,如果后面if条件中有成立的语句,则加上where语句,如果没有成立的条件语句,则不加where条件,第一个成立条件的语句,前面的and或者or自动删除。

3.3 <trim>标签

xml 复制代码
<trim prefix="前缀" suffix="后缀" prefixOverrides="忽略前缀字符" suffixOverrides="忽略后缀字符">
    SQL语句
</trim>
  • 功能:自定义前缀/后缀的添加或删除,适用于复杂场景。
  • 属性prefix(添加前缀)、suffix(添加后缀)、prefixOverrides(删除前缀)、suffixOverrides(删除后缀)。

trim 中属性说明如下。

属性 描述
prefix 给SQL语句拼接的前缀,为 trim 包含的内容加上前缀
suffix 给SQL语句拼接的后缀,为 trim 包含的内容加上后缀
prefixOverrides 去除 SQL 语句前面的关键字或字符,该关键字或者字符由 prefixOverrides 属性指定。
suffixOverrides 去除 SQL 语句后面的关键字或者字符,该关键字或者字符由 suffixOverrides 属性指定。
  • 示例:替代<where>实现相同功能:

    xml 复制代码
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <if test="name != null">AND name = #{name}</if>
    </trim>

3.4 <choose><when><otherwise>标签

  • 功能 :实现多分支选择逻辑,类似Java的switch-case-default

xml 复制代码
<choose>
    <when test="判断条件1">
        SQL语句1
    </when>
    <when test="判断条件2">
        SQL语句2
    </when>
    <when test="判断条件3">
        SQL语句3
    </when>
    <otherwise>
        SQL语句4
    </otherwise>
</choose>
  • 示例:

    xml 复制代码
    <choose>
        <when test="name != null">AND name = #{name}</when>
        <when test="age != null">AND age = #{age}</when>
        <otherwise>AND status = 1</otherwise>
    </choose>

3.5 <foreach>标签

对于一些 SQL 语句中含有 in 条件,需要迭代条件集合来生成的情况,可以使用 foreach 来实现 SQL 条件的迭代。

xml 复制代码
<foreach item="item" index="index" collection="list|array|map key" open="(" separator="," close=")">
    参数值
</foreach>  

参数说明:

foreach 标签主要有以下属性,说明如下。

  • item:表示集合中每一个元素进行迭代时的别名。
  • index:指定一个名字,表示在迭代过程中每次迭代到的位置。
  • open:表示该语句以什么开始(既然是 in 条件语句,所以必然以(开始)。
  • separator:表示在每次进行迭代之间以什么符号作为分隔符(既然是 in 条件语句,所以必然以,作为分隔符)。
  • close:表示该语句以什么结束(既然是 in 条件语句,所以必然以)开始)。

使用 foreach 标签时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:

  • 如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
  • 如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
  • 如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
  • 功能 :遍历集合(如List、Array),生成批量操作的SQL,常用于IN语句或批量插入。

  • 示例:根据ID列表查询用户:

    XML 复制代码
    SELECT * FROM user WHERE id IN
    <foreach collection="idList" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>

3.6 <set>标签

  • 功能 :在更新操作中动态生成SET子句,自动去除末尾逗号。

  • 示例:

    XML 复制代码
    <update id="updateUser">
        UPDATE user
        <set>
            <if test="name != null">name = #{name},</if>
            <if test="age != null">age = #{age}</if>
        </set>
        WHERE id = #{id}
    </update>

3.7 bind标签

每个数据库的拼接函数或连接符号都不同,例如 MySQL 的 concat 函数、Oracle 的连接符号"||"等。这样 SQL 映射文件就需要根据不同的数据库提供不同的实现,显然比较麻烦,且不利于代码的移植。幸运的是,MyBatis 提供了 bind 标签来解决这一问题。

bind 标签可以通过 OGNL 表达式自定义一个上下文变量。

比如,按照名称进行模糊查询,SQL 映射文件如下。

xml 复制代码
<select id="select14e" resultType="Student">
    <bind name="myvalue" value="'%'+stu_name+'%'" />
    SELECT *
    FROM student
    WHERE stu_name like #{myvalue}
</select>            

bind 元素属性如下:

  • value:对应传入实体类的某个字段,可以进行字符串拼接等特殊处理。
  • name:给对应参数取的别名。

以上代码中的"_parameter"代表传递进来的参数,它和通配符连接后,赋给了 pattern,然后就可以在 select 语句中使用这个变量进行模糊查询,不管是 MySQL 数据库还是 Oracle 数据库都可以使用这样的语句,提高了可移植性。

4.使用建议与注意事项

  1. 优先使用<where><set>:避免手动添加1=1或处理逗号,提高代码简洁性

  2. 模糊查询优化:使用<bind>标签统一处理模糊匹配,如<bind name="nameLike" value="'%' + name + '%'" />,解决不同数据库的语法差异

  3. 性能考量:动态SQL可能影响执行计划优化,复杂条件建议结合索引设计使用

相关推荐
charlie1145141918 分钟前
从0开始的操作系统手搓教程27:下一步,实现我们的用户进程
学习·架构·系统架构·操作系统·教程·手搓教程·用户线程
Vacant Seat9 分钟前
图论-实现Trie(前缀树)
java·开发语言·数据结构·图论
工一木子23 分钟前
【HeadFirst系列之HeadFirstJava】第16天之深入解析 Java 集合与泛型:高效管理数据的终极指南!(含代码实战)
java·集合·泛型
Yuanymoon1 小时前
【由技及道】统一封装API返回结果后String返回报错文件解决原理--Spring 消息转换器的层次图解与规则说明【人工智障AI2077的开发问题日志002】
java·spring
听风说雨的人儿1 小时前
ES6 class的继承概念
java·前端·es6
瑶光守护者1 小时前
并行计算编程模型的发展方向与RISC-V的机遇
人工智能·笔记·学习·架构·risc-v
l_tian_tian_1 小时前
JavaWeb——Mybatis、JDBC、数据库连接池、lombok
java·数据库·mybatis
半夏知半秋1 小时前
linux下的网络抓包(tcpdump)介绍
linux·运维·服务器·网络·笔记·学习·tcpdump
臣妾写不来啊1 小时前
使用dify的api连接外部知识库,dify连接ragflow的知识库(附java代码)
java·开发语言·spring boot
m0_748247801 小时前
Python连接SQL SEVER数据库全流程
数据库·python·sql