news 2026/4/16 12:35:10

MyBatis 动态 SQL 全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis 动态 SQL 全攻略

适合零基础到进阶:解释每个动态标签的作用、使用场景、常见坑,并给出基于本项目的示例(User/Student/Teacher)。代码含详尽注释,便于直接参考。


1. 为什么需要动态 SQL?

  • 业务条件多且可选(多条件查询、可选更新字段)。
  • 需要批量操作(批量插入/删除)。
  • 避免手写字符串拼接和 if/else 造成的代码混乱,同时提升安全性(使用#{}预编译占位符,防 SQL 注入)。

2. 动态 SQL 的核心标签概览

  • <if>:条件成立则拼接片段。
  • <where>:自动处理首个条件前的AND/OR并在无条件时去掉WHERE
  • <set>:更新语句中自动去掉末尾逗号。
  • <trim>:自定义前缀/后缀及多余字符的裁剪(prefixOverridessuffixOverrides)。
  • <choose>/<when>/<otherwise>:相当于 SQL 里的 if/else if/else。
  • <foreach>:循环,常用于IN (...)、批量插入。
  • <bind>:定义局部变量,构造更灵活的条件(本文示例主要用前几个标签)。

3. 项目结构与前置

  • 实体:entity.User/Student/Teacher(字段:id, name, age, birthday, gender, phone)。
  • Mapper 接口:UserDaoStudentDaoTeacherDao
  • Mapper XML:UserMapper.xmlStudentMapper.xmlTeacherMapper.xml
  • 测试:UserTestStudentTestTeacherTest

4. 基础示例:多条件查询(<where> + <if>

示例(User):

<selectid="dtFindUser"parameterType="entity.User"resultType="entity.User">select * from user<where><!-- 有条件时自动加 WHERE 并去掉首个 AND/OR --><iftest="name != null">and name like concat('%', #{name}, '%')<!-- 仅当 name 非空才拼 --></if><iftest="gender != null">and gender = #{gender}</if><iftest="phone != null">and phone like concat('%', #{phone}, '%')</if></where></select>

要点:

  • <where>会:
    1. 在有条件时自动加上WHERE
    2. 自动去掉首个条件前多余的AND/OR
  • #{}是预编译占位符,安全防注入。
  • 传入一个User对象,非 null 的字段才会生效。

扩展到 Student/Teacher 只需改表名和类型,已在StudentMapper.xml/TeacherMapper.xml中实现。


5. 动态更新(<set> + <if>

示例(User):

<updateid="dtUpdate"parameterType="entity.User">update user<set><!-- 自动去掉最后一个逗号,避免 SQL 语法错误 --><iftest="name != null">name = #{name},</if><iftest="gender != null">gender = #{gender},</if><iftest="phone != null">phone = #{phone},</if><iftest="age != null">age = #{age},</if><iftest="birthday != null">birth_date = #{birthday},</if></set>where id = #{id}<!-- 必填,防止全表更新 --></update>

要点:

  • <set>自动去掉最后一个逗号,避免语法错误。
  • 仅更新非空字段,常用于“部分字段更新”。

Student/Teacher 对应方法:dtUpdateStudentdtUpdateTeacher


6. 灵活裁剪(<trim>

6.1 动态 where + trim

<selectid="trFindUser"parameterType="entity.User"resultType="entity.User">select * from user<trimprefix="where"prefixOverrides="and | or"><!-- 自定义 where,裁掉首个 AND/OR --><iftest="name != null">and name like concat('%', #{name}, '%')</if><iftest="gender != null">and gender = #{gender}</if><iftest="phone != null">and phone like concat('%', #{phone}, '%')</if></trim></select>

要点:

  • prefix="where":有条件时自动加WHERE
  • prefixOverrides="and | or":去掉首个多余的AND/OR

6.2 动态 set + trim

<updateid="trUpdateUser"parameterType="entity.User">update user<trimprefix="set"suffixOverrides=","><!-- 功能类似 set,但可自定义裁剪 --><iftest="name != null">name = #{name},</if><iftest="gender != null">gender = #{gender},</if><iftest="phone != null">phone = #{phone},</if><iftest="age != null">age = #{age},</if><iftest="birthday != null">birth_date = #{birthday},</if></trim>where id = #{id}</update>
  • suffixOverrides=",":去掉最后的逗号,等效于<set>,但更自由(可自定义前后缀)。

Student/Teacher 已有同名的trFind*/trUpdate*版本。


7. 选择分支(<choose>/<when>/<otherwise>

示例(User):

<selectid="selectUserByChoose"resultType="entity.User"parameterType="entity.User">select * from user<where><choose><!-- 命中第一个 when 后就停止,适合按优先级选条件 --><whentest="name != null">name = #{name}</when><whentest="gender != null">gender = #{gender}</when><whentest="phone != null">phone = #{phone}</when><otherwise>id = #{id}</otherwise></choose></where></select>

要点:

  • 类似 if / else if / else。
  • 一旦命中某个<when>,后续不会再执行。
  • 常用于“优先级条件”场景,例如先按 name,没 name 再按 gender 等。

Student/Teacher 中有对应的selectStudentByChoose/selectTeacherByChoose


8. 批量操作(<foreach>

8.1 批量删除

<deleteid="deleteMore">delete from user where id in<foreachitem="id"collection="ids"index="index"open="("close=")"separator=",">#{id}<!-- 逐个绑定,防注入 --></foreach></delete>

要点:

  • collection="ids"对应接口参数名@Param("ids") List<Integer> ids
  • open/close/separator控制括号与分隔符。

8.2 批量插入

<insertid="insertMore">insert into user (name, gender, phone, age, birth_date) values<foreachitem="user"collection="users"index="index"separator=",">(#{user.name}, #{user.gender}, #{user.phone}, #{user.age}, #{user.birthday})<!-- 拼成多行 values --></foreach></insert>

要点:

  • collection="users"对应接口参数名@Param("users") List<User> users
  • 每个元素生成一组(...),用逗号分隔。

Student/Teacher 有同名的批量方法:deleteMoreinsertMore


9. 模糊查询与防注入

安全写法:

<selectid="likeByName2"resultType="entity.User"parameterType="java.lang.String">select * from user where name like concat('%', #{value}, '%')</select>
  • #{}预编译占位符,安全。
  • 避免${}直接拼接,除非做白名单校验的动态表/列名。

(本项目中likeByName使用${value}的写法仅作对比示例,实际推荐likeByName2。)


10. 回填主键(<selectKey>

<insertid="insertGetId"parameterType="entity.User">insert into user (name, gender, phone, age, birth_date) values(#{name}, #{gender}, #{phone}, #{age}, #{birthday})<selectKeykeyProperty="id"resultType="java.lang.Integer"order="AFTER">select last_insert_id()</selectKey></insert>

要点:

  • order="AFTER":先插入,再查自增主键。
  • keyProperty="id":把生成的主键回填到入参对象的id字段。
  • Student/Teacher 也有insertGetId

11. Java 测试用例示例(以 User 为例,Student/Teacher 类似)

@TestpublicvoiddtFindUser(){Useruser=newUser();user.setName("张");// 只给 name,动态 where 会生成 name like '%张%'List<User>users=mapper.dtFindUser(user);users.forEach(System.out::println);}@TestpublicvoiddtUpdate(){Useruser=newUser();user.setId(13);user.setName("张三");// 只更新 nameintcount=mapper.dtUpdate(user);System.out.println(count);}@TestpublicvoiddeleteMore(){List<Integer>ids=newArrayList<>();ids.add(1);ids.add(2);intcount=mapper.deleteMore(ids);System.out.println(count);}@TestpublicvoidinsertMore(){List<User>users=newArrayList<>();users.add(newUser("批量1",18,newDate(),"男","1111"));users.add(newUser("批量2",19,newDate(),"女","2222"));intcount=mapper.insertMore(users);System.out.println(count);}

运行说明:

  1. 确保SqlMapConfig.xml已注册对应 Mapper。
  2. 用 JUnit 运行测试,控制台可看到 STDOUT_LOGGING 打印的 SQL。
  3. 事务在@Aftersession.commit(),写操作会提交。

12. 常见坑与规避

  • 参数名不一致:接口参数名、XML 中的占位符/集合名必须一致;多个参数时可用@Param
  • ${}注入风险:优先用#{};若必须用${}(动态列/表),先做白名单校验。
  • 空条件导致全表更新/删除:动态 SQL 条件为空时要谨慎,可在 Java 侧校验,或在 XML 中加兜底(如otherwise限制)。
  • 末尾逗号<set>suffixOverrides=","处理;不要手写逗号。
  • choose 优先级:命中第一个<when>后不会再看其他条件,顺序要慎重。

13. 常见面试题与简答

  1. #{} 和 ${}区别?

    • #{}预编译占位,防注入;${}字符串直拼,易注入,仅在动态表/列名等必须场景使用且要白名单。
  2. <where><trim prefix="where">有何不同?

    • <where>自动加WHERE并去掉首个 AND/OR;<trim>更灵活,可自定义前后缀与裁剪规则。
  3. <set><trim prefix="set" suffixOverrides=",">的区别?

    • 效果类似,<trim>可自定义更复杂的裁剪逻辑,<set>更简洁专用于 UPDATE。
  4. <choose>适用场景?

    • 有优先级的条件选择,类似 if/else if/else,一次只命中一个分支。
  5. 批量操作用什么标签?

    • <foreach>,常见用于IN (...)和批量插入。
  6. 如何防止动态更新把所有字段清空?

    • 在 Java 侧校验至少有一个可更新字段;或在 XML 用<set>包裹<if>,确保where id = ...必填。
  7. selectKey的作用?

    • 获取插入后的主键并回填到对象属性;order可选 BEFORE/AFTER。
  8. 动态 SQL 是否影响性能?

    • MyBatis 会生成最终 SQL 并预编译,性能主要取决于 SQL 本身和索引;关键是保持条件有索引、避免全表扫描。

14. 实践清单(照着做)

  1. 按需挑选标签:多条件用<where> + <if>,优先级用<choose>,动态更新用<set>/<trim>,批量用<foreach>
  2. 参数命名对齐:接口参数名 + XML 占位符一致,多个参数用@Param
  3. 防注入:能用#{}不用${};动态表/列名必须白名单。
  4. 提交事务:写操作后记得commit();或openSession(true)
  5. 打日志:<setting name="logImpl" value="STDOUT_LOGGING"/>便于排查。

15. 结语

掌握动态 SQL 的几个标签组合,就能写出可维护、可扩展的查询和更新语句。建议多练习:

  • 将你的条件从 1 个扩展到 N 个,看 SQL 是否按预期生成;
  • 尝试批量插入/删除,观察生成的 SQL;
  • choose做优先级控制,避免多重 if 嵌套。

祝学习顺利,面试稳稳拿下! 😊

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 0:38:08

10、云数据库分片、多租户与通用硬件应用解析

云数据库分片、多租户与通用硬件应用解析 数据库分片与联邦特性 在云数据库应用中,数据库分片是一种重要的扩展技术。而联邦(Federations)特性是云数据库中一种特殊的分片实现方式,它有着独特的术语体系。在 Windows Azure SQL 数据库里,联邦相当于分片(shard),联邦键…

作者头像 李华
网站建设 2026/4/16 3:03:06

手把手教你搭建8位加法器:FPGA实现操作指南

从零开始构建8位加法器&#xff1a;FPGA实战全记录你有没有想过&#xff0c;计算机是怎么做加法的&#xff1f;不是打开计算器点几下&#xff0c;而是在硬件层面——通过成千上万个晶体管协同工作&#xff0c;用0和1完成一次精准的算术运算。今天&#xff0c;我们就来“造一个轮…

作者头像 李华
网站建设 2026/4/15 13:32:14

Photoshop图层批量导出终极指南:3分钟掌握高效工作流

Photoshop图层批量导出终极指南&#xff1a;3分钟掌握高效工作流 【免费下载链接】Photoshop-Export-Layers-to-Files-Fast This script allows you to export your layers as individual files at a speed much faster than the built-in script from Adobe. 项目地址: http…

作者头像 李华
网站建设 2026/4/10 22:17:44

Midscene.js自动化报告生成:如何解决测试结果可视化难题

自动化测试的痛点往往不在执行本身&#xff0c;而在于如何清晰展示复杂的操作过程和AI决策逻辑。Midscene.js作为AI驱动的浏览器自动化工具&#xff0c;其报告生成功能正是为解决这一难题而生&#xff0c;让每一次自动化操作都变得透明可追溯。 【免费下载链接】midscene Let A…

作者头像 李华
网站建设 2026/4/14 16:22:08

5个步骤轻松掌握Windows优化神器WinClean:让你的系统飞起来!

5个步骤轻松掌握Windows优化神器WinClean&#xff1a;让你的系统飞起来&#xff01; 【免费下载链接】WinClean Windows optimization and debloating utility. 项目地址: https://gitcode.com/gh_mirrors/wi/WinClean 还在为Windows系统卡顿、预装软件过多而烦恼吗&…

作者头像 李华
网站建设 2026/4/13 13:32:19

教育科技融合典范:学生用Anything-LLM做毕业论文辅助

教育科技融合典范&#xff1a;学生用Anything-LLM做毕业论文辅助 在高校毕业季&#xff0c;无数学生正为文献综述焦头烂额——面对几十篇PDF格式的学术论文&#xff0c;逐页翻阅、手动摘录、反复比对观点&#xff0c;不仅耗时费力&#xff0c;还容易遗漏关键信息。更令人头疼的…

作者头像 李华