当前位置: 首页 > news >正文

MyBatis 动态 SQL 详解:优雅处理复杂查询场景

一、动态 SQL 概述:为什么需要动态 SQL?

在实际开发中,数据库查询往往不是固定不变的,而是需要根据不同条件动态生成 SQL 语句。例如:

  • 多条件查询时,用户可能输入部分条件或全部条件
  • 数据更新时,可能只更新部分字段
  • 批量操作时,需要根据集合长度生成对应 SQL

如果手动拼接 SQL 字符串,会面临以下问题:

  • 繁琐的字符串拼接逻辑
  • 容易出现语法错误(如多余的 AND/OR)
  • 代码可读性和可维护性差
  • 潜在的 SQL 注入风险

MyBatis 的动态 SQL 功能完美解决了这些问题,它允许在 XML 映射文件中使用标签编写动态逻辑,自动生成符合条件的 SQL 语句。本文将全面讲解 MyBatis 动态 SQL 的常用标签和实战场景。

二、if 标签:条件判断的基础

if标签是动态 SQL 中最基本的条件判断标签,用于根据参数值决定是否拼接某段 SQL。

2.1 基本用法

Mapper 接口

java

运行

// 多条件查询
List<User> queryUserByCondition(@Param("username") String username,@Param("age") Integer age,@Param("email") String email);

映射文件

xml

<select id="queryUserByCondition" resultType="User">SELECT * FROM user WHERE 1=1<if test="username != null and username != ''">AND username LIKE CONCAT('%', #{username}, '%')</if><if test="age != null">AND age = #{age}</if><if test="email != null and email != ''">AND email = #{email}</if>
</select>

说明

  • test属性:判断条件表达式
  • 当条件为true时,拼接标签内的 SQL 片段
  • 使用WHERE 1=1避免条件都不满足时的 SQL 语法错误

2.2 常见判断条件

条件表达式
非空判断test="param != null"
字符串非空且非空串test="param != null and param != ''"
集合非空test="list != null and list.size() > 0"
数值比较test="age > 18"
字符串包含test="username.indexOf('张') != -1"
多条件或test="age > 18 or score > 90"

2.3 注意事项

  • 字符串判断需同时检查null和空串
  • 数值类型只需检查null(空串会转换为 0)
  • 条件表达式中使用and/or而非&&/||
  • 字符串比较使用==而非equals()方法

三、where/trim 标签:智能处理条件拼接

where标签用于自动处理条件拼接中的AND/OR关键字,替代传统的WHERE 1=1写法。

3.1 where 标签用法

xml

<select id="queryUserByCondition" resultType="User">SELECT * FROM user<where><if test="username != null and username != ''">AND username LIKE CONCAT('%', #{username}, '%')</if><if test="age != null">AND age = #{age}</if><if test="email != null and email != ''">AND email = #{email}</if></where>
</select>

where 标签的作用

  • 当内部有条件成立时,自动添加WHERE关键字
  • 自动去除条件片段开头的ANDOR
  • 当所有条件都不成立时,不添加WHERE关键字

3.2 trim 标签:更灵活的条件处理

trim标签通过属性配置,可以实现where标签的功能,甚至更多:

xml

<select id="queryUserByCondition" resultType="User">SELECT * FROM user<trim prefix="WHERE" prefixOverrides="AND | OR"><if test="username != null and username != ''">AND username LIKE CONCAT('%', #{username}, '%')</if><if test="age != null">AND age = #{age}</if></trim>
</select>

trim 标签属性

  • prefix:拼接在 trim 内容前的字符串
  • suffix:拼接在 trim 内容后的字符串
  • prefixOverrides:需要从内容开头去除的字符串
  • suffixOverrides:需要从内容结尾去除的字符串

四、set 标签:动态更新字段

set标签用于动态更新场景,自动处理字段后的逗号。

4.1 基本用法

Mapper 接口

java

运行

// 动态更新用户信息
int updateUserSelective(User user);

映射文件

xml

<update id="updateUserSelective" parameterType="User">UPDATE user<set><if test="username != null and username != ''">username = #{username},</if><if test="password != null and password != ''">password = #{password},</if><if test="age != null">age = #{age},</if><if test="email != null and email != ''">email = #{email}</if></set>WHERE id = #{id}
</update>

set 标签的作用

  • 自动添加SET关键字
  • 自动去除字段后的逗号
  • 当没有字段需要更新时,会导致 SQL 错误(需避免)

4.2 使用 trim 模拟 set 标签

xml

<trim prefix="SET" suffixOverrides=","><if test="username != null">username = #{username},</if><if test="age != null">age = #{age},</if>
</trim>

五、foreach 标签:集合迭代处理

foreach标签用于遍历集合或数组,常用于IN查询和批量操作。

5.1 基本用法(IN 查询)

Mapper 接口

java

运行

// 批量查询
List<User> getUserByIds(@Param("ids") List<Integer> ids);

映射文件

xml

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

5.2 批量插入

Mapper 接口

java

运行

// 批量插入
int batchInsert(@Param("users") List<User> users);

映射文件

xml

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

5.3 批量更新(MySQL)

xml

<update id="batchUpdate"><foreach collection="users" item="user" separator=";">UPDATE userSET username = #{user.username},age = #{user.age}WHERE id = #{user.id}</foreach>
</update>

注意:MySQL 需要开启多语句执行支持(在 URL 后添加allowMultiQueries=true

5.4 foreach 标签属性详解

属性作用
collection集合参数名称(必填)
item迭代变量名(必填)
index索引变量名(可选)
open拼接在迭代内容前的字符串
close拼接在迭代内容后的字符串
separator元素间的分隔符
itemType集合元素类型(可选)

六、choose/when/otherwise 标签:多条件分支

choose标签类似 Java 中的switch语句,只执行第一个满足条件的when分支。

6.1 基本用法

Mapper 接口

java

运行

// 复杂条件查询
List<User> queryUserByChoose(@Param("username") String username,@Param("age") Integer age,@Param("email") String email);

映射文件

xml

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

说明

  • choose:包裹所有条件分支
  • when:条件分支,类似case
  • otherwise:默认分支,类似default
  • 只会执行第一个满足条件的when,其他分支会被忽略

七、bind 标签:创建可复用的变量

bind标签用于在 SQL 语句中创建变量,通常用于模糊查询的字符串拼接。

7.1 基本用法

xml

<select id="getUserByUsername" resultType="User"><bind name="pattern" value="'%' + username + '%'"/>SELECT * FROM userWHERE username LIKE #{pattern}
</select>

优势

  • 避免数据库方言差异(MySQL 用CONCAT,Oracle 用||
  • 提高代码可移植性
  • 可在多个地方复用变量

八、sql/include 标签:SQL 片段复用

sql标签用于定义可复用的 SQL 片段,include标签用于引用这些片段。

8.1 基本用法

xml

<!-- 定义SQL片段 -->
<sql id="userColumns">id, username, password, age, email
</sql><!-- 引用SQL片段 -->
<select id="getUserById" resultType="User">SELECT <include refid="userColumns"/> FROM userWHERE id = #{id}
</select><select id="getAllUsers" resultType="User">SELECT <include refid="userColumns"/> FROM user
</select>

8.2 带参数的 SQL 片段

xml

<sql id="whereCondition"><where><if test="param != null">AND ${column} = #{param}</if></where>
</sql><select id="queryByColumn" resultType="User">SELECT * FROM user<include refid="whereCondition"><property name="column" value="username"/></include>
</select>

九、动态 SQL 实战案例

9.1 复杂查询场景

实现一个支持分页、排序、多条件的用户查询功能:

Mapper 接口

java

运行

List<User> queryUserAdvanced(@Param("username") String username,@Param("minAge") Integer minAge,@Param("maxAge") Integer maxAge,@Param("email") String email,@Param("orderColumn") String orderColumn,@Param("orderType") String orderType,@Param("start") Integer start,@Param("pageSize") Integer pageSize
);

映射文件

xml

<select id="queryUserAdvanced" resultType="User">SELECT id, username, age, email FROM user<where><if test="username != null and username != ''">AND username LIKE CONCAT('%', #{username}, '%')</if><if test="minAge != null">AND age >= #{minAge}</if><if test="maxAge != null">AND age <= #{maxAge}</if><if test="email != null and email != ''">AND email LIKE CONCAT('%', #{email}, '%')</if></where><if test="orderColumn != null and orderColumn != ''">ORDER BY ${orderColumn}<if test="orderType != null and orderType != ''">${orderType}</if><if test="orderType == null or orderType == ''">ASC</if></if><if test="start != null and pageSize != null">LIMIT #{start}, #{pageSize}</if>
</select>

9.2 动态更新非空字段

xml

<update id="updateUserDynamic">UPDATE user<trim prefix="SET" suffixOverrides=","><if test="username != null and username != ''">username = #{username},</if><if test="password != null and password != ''">password = #{password},</if><if test="age != null">age = #{age},</if><if test="email != null and email != ''">email = #{email},</if><if test="updateTime != null">update_time = #{updateTime}</if></trim>WHERE id = #{id}
</update>

十、动态 SQL 最佳实践

  1. 保持 SQL 片段简洁

    • 每个动态 SQL 片段专注于单一功能
    • 避免嵌套过深(建议不超过 3 层)
  2. 合理使用 SQL 片段复用

    • 将常用字段列表、条件判断封装为 SQL 片段
    • 避免过度复用导致的维护困难
  3. 注意 SQL 注入风险

    • 动态排序、表名使用${}时必须进行白名单校验
    • 示例:

    java

    运行

    // 排序字段白名单校验
    if (!Arrays.asList("id", "username", "age").contains(orderColumn)) {orderColumn = "id"; // 默认值
    }
    // 排序方向校验
    if (!Arrays.asList("ASC", "DESC").contains(orderType)) {orderType = "ASC";
    }
    
  4. 性能考虑

    • 避免不必要的条件判断
    • 复杂动态 SQL 可考虑拆分为多个方法
  5. 测试覆盖

    • 为动态 SQL 的各种分支场景编写测试用例
    • 确保所有条件组合都能正确生成 SQL

十一、常见问题与解决方案

  1. 动态 SQL 生成错误的 SQL

    • 启用日志打印生成的 SQL(logImpl=LOG4J
    • 检查条件判断逻辑和标签嵌套
  2. foreach 标签 collection 属性错误

    • 单个集合参数且无@Param时,collection 值为list(List)或array(数组)
    • 使用@Param注解明确指定集合名称
  3. set 标签导致的 SQL 语法错误

    • 确保至少有一个字段会被更新
    • 在 Service 层进行参数校验
  4. 模糊查询在不同数据库的兼容性

    • 使用bind标签统一处理模糊查询
    • 避免直接使用数据库特定函数

总结

MyBatis 的动态 SQL 是处理复杂查询场景的强大工具,通过ifwhereforeach等标签,可以优雅地生成各种条件的 SQL 语句,避免手动拼接的繁琐和错误。本文详细介绍了动态 SQL 的常用标签和实战案例,掌握这些知识能够帮助你应对各种复杂的查询需求。在实际开发中,应合理运用动态 SQL,同时注意 SQL 安全和性能问题,编写高效、可维护的数据库访问代码。

http://www.dtcms.com/a/392408.html

相关文章:

  • 如何看待Qt中的QObject这个类
  • utf8mb4_bin 与 utf8mb4_generate_cli区别
  • CAN总线学习(一)CAN总线通讯&硬件电路
  • 13. LangChain4j + 加入检索增加生成 RAG(知识库)
  • TriggerRecovery
  • OpenAI 开源 GPT-oss 模型:从闭源到开源的模型架构创新之路
  • 微服务技术栈一文串讲
  • 从浅入深:自编码器(AE)与变分自编码器(VAE)的核心原理与Pytorch代码讲解
  • 低功耗超宽带收发器:DW1000设备驱动API指南
  • 2012/07 JLPT听力原文 问题四
  • Redis最佳实践——性能优化技巧之集群与分片
  • springboot的注解
  • iOS App 混淆与热更新兼容实战 混淆后如何安全可靠地推送热修复(Hotfix)与灰度回滚
  • 从 0 到 1 保姆级实现C语言双向链表
  • 2 IP地址规划与设计案例分析
  • Vue 中 8 种组件通信方式
  • 十三、vue3后台项目系列——sidebar的实现,递归组件
  • LeetCode 383 - 赎金信
  • compose multiplatform reader3
  • Redis 入门与实践
  • 【OpenGL】texture 纹理
  • agentscope以STUDIO方式调用MCP服务
  • 无公网 IP 访问群晖 NAS:神卓 N600 的安全解决方案(附其他方法风险对比)
  • Redis最佳实践——性能优化技巧之Pipeline 批量操作
  • Java-130 深入浅出 MySQL MyCat 深入解析 核心配置文件 server.xml 使用与优化
  • 业主信息查询功能测试指南
  • WinDivert学习文档之四-————卸载
  • 分布式链路追踪关键指标实战:精准定位服务调用 “慢节点” 全指南(二)
  • DuckDB客户端API之ADBC官方文档翻译
  • 区块链技术应用开发:智能合约进阶与多链生态落地实践