目录

有关MyBatis的动态SQL

有关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 语句

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

    <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记录。但是,如果你传递了任意一个参数,它就会返回与给定参数相匹配的记录。
    <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() 避免类型错误,如

    test="sex == '1'.toString()"

3.2 <where> 标签

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

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

  • 示例:

    <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> 标签

<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> 实现相同功能:

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

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

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

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

    <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 条件的迭代。

<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列表查询用户:

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

3.6 <set> 标签

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

  • 示例:

    <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 映射文件如下。

<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可能影响执行计划优化,复杂条件建议结合索引设计使用