第07章 动态SQL
学习动态 SQL 的编写和各种动态标签的使用
学习进度:28% 代码演示按钮 💻 查看完整代码 - 在线IDE体验
🎯 学习目标
📚
理解核心概念
掌握本章的核心概念和基本原理
🔧
掌握实践技能
学会相关的配置方法和实践技巧
💡
了解应用场景
明确在实际项目中的应用场景和最佳实践
学习目标
- 深入理解MyBatis动态SQL的概念和优势
- 掌握if、choose、when、otherwise等条件标签
- 学会使用where、set、trim等辅助标签
- 熟练运用foreach标签进行批量操作
- 掌握SQL片段的定义和复用技巧
- 了解动态SQL的最佳实践和性能优化
动态SQL概述
动态SQL是MyBatis的强大特性之一,它允许我们根据不同的条件动态构建SQL语句,避免了在Java代码中拼接SQL字符串的复杂性和安全风险。
动态SQL的优势
- 灵活性:根据条件动态生成SQL
- 安全性:避免SQL注入风险
- 可维护性:SQL逻辑集中管理
- 性能优化:只查询需要的字段和条件
if标签
if标签是最基本的条件判断标签,用于根据条件决定是否包含某段SQL。
基本语法
<if test="条件表达式">
SQL片段
</if>
实际应用
<select id="selectUsersByCondition" resultType="User">
SELECT id, name, age, email FROM users
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
</select>
条件表达式
- name != null:判断参数是否为null
- name != '':判断字符串是否为空
- age > 0:数值比较
- list != null and list.size() > 0:集合判断
choose/when/otherwise标签
类似于Java中的switch语句,用于多条件选择,只会执行其中一个分支。
基本语法
<choose>
<when test="条件1">
SQL片段1
</when>
<when test="条件2">
SQL片段2
</when>
<otherwise>
默认SQL片段
</otherwise>
</choose>
实际应用
<select id="selectUsersByChoice" resultType="User">
SELECT id, name, age, email FROM users
WHERE 1=1
<choose>
<when test="name != null and name != ''">
AND name = #{name}
</when>
<when test="age != null">
AND age = #{age}
</when>
<when test="email != null and email != ''">
AND email = #{email}
</when>
<otherwise>
AND 1=1
</otherwise>
</choose>
</select>
where标签
where标签用于动态生成WHERE子句,自动处理AND和OR的前缀问题。
解决的问题
- 自动添加WHERE关键字
- 自动去除多余的AND或OR前缀
- 当没有条件时,不生成WHERE子句
实际应用
<select id="selectUsersByMap" parameterType="map" resultType="User">
SELECT id, name, age, email FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</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 = #{email}
</if>
</where>
</select>
set标签
set标签用于动态生成SET子句,常用于UPDATE语句中的选择性更新。
解决的问题
- 自动添加SET关键字
- 自动去除多余的逗号
- 只更新非空字段
实际应用
<update id="updateUserSelective" parameterType="User">
UPDATE users
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
</set>
WHERE id = #{id}
</update>
foreach标签
foreach标签用于遍历集合,常用于IN查询、批量插入、批量删除等场景。
属性说明
- collection:要遍历的集合
- item:集合中每个元素的别名
- index:当前元素的索引
- open:开始字符
- close:结束字符
- separator:分隔符
IN查询示例
<select id="selectUsersByIds" resultType="User">
SELECT id, name, age, email FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
批量插入示例
<insert id="batchInsertUsers">
INSERT INTO users (name, age, email) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email})
</foreach>
</insert>
批量删除示例
<delete id="batchDeleteUsers">
DELETE FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
trim标签
trim标签是最灵活的标签,可以自定义前缀、后缀,以及要去除的前缀和后缀。
属性说明
- prefix:添加的前缀
- suffix:添加的后缀
- prefixOverrides:要去除的前缀
- suffixOverrides:要去除的后缀
实际应用
<select id="selectUsersByTrim" resultType="User">
SELECT id, name, age, email FROM users
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</trim>
</select>
等价关系
<!-- where标签等价于 -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
<!-- set标签等价于 -->
<trim prefix="SET" suffixOverrides=",">
...
</trim>
SQL片段
SQL片段用于定义可重用的SQL代码块,提高代码复用性和维护性。
定义SQL片段
<!-- 定义常用的列名 -->
<sql id="userColumns">
id, name, age, email
</sql>
<!-- 定义表名 -->
<sql id="userTable">
users
</sql>
<!-- 定义常用的WHERE条件 -->
<sql id="userWhereCondition">
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</sql>
引用SQL片段
<select id="selectUsersWithFragment" resultType="User">
SELECT <include refid="userColumns"/>
FROM <include refid="userTable"/>
<include refid="userWhereCondition"/>
ORDER BY id
</select>
带参数的SQL片段
<sql id="orderBy">
ORDER BY ${column} ${direction}
</sql>
<select id="selectUsersWithOrder" resultType="User">
SELECT <include refid="userColumns"/> FROM <include refid="userTable"/>
<include refid="orderBy">
<property name="column" value="name"/>
<property name="direction" value="ASC"/>
</include>
</select>
动态SQL最佳实践
1. 性能优化
- 避免在动态SQL中使用复杂的逻辑判断
- 合理使用索引,确保动态生成的WHERE条件能够利用索引
- 批量操作时控制批次大小,避免单次操作数据量过大
- 使用合适的参数类型,避免不必要的类型转换
2. 安全考虑
- 始终使用#{}进行参数绑定,避免SQL注入
- 谨慎使用${},仅在必要时使用(如动态表名、列名)
- 对用户输入进行验证和过滤
- 避免在动态SQL中直接拼接用户输入
3. 代码组织
- 合理使用SQL片段,提高代码复用性
- 保持SQL语句的可读性,适当添加注释
- 将复杂的动态SQL拆分为多个简单的片段
- 统一命名规范,便于维护
4. 调试技巧
- 开启SQL日志,查看实际生成的SQL语句
- 使用单元测试验证各种条件分支
- 在开发环境中打印SQL参数
- 使用数据库工具验证SQL语句的正确性
常见问题与解决方案
Q: 动态SQL中的条件判断不生效?
A: 检查test表达式是否正确,注意参数名称和类型,确保传入的参数不为null。
Q: foreach标签遍历集合时出现异常?
A: 确保collection属性指向的集合不为null,检查item和index的命名是否正确。
Q: where标签没有自动去除AND前缀?
A: 确保在if条件中使用AND或OR作为前缀,where标签会自动处理这些前缀。
Q: SQL片段引用时找不到?
A: 检查sql片段的id是否正确,确保在同一个mapper文件中或者使用完整的命名空间引用。
Q: 动态SQL性能较差?
A: 检查生成的SQL是否能够利用索引,避免全表扫描,合理设计查询条件。
实践练习
练习1:条件查询
实现一个用户搜索功能,支持按姓名、年龄范围、邮箱进行模糊查询。
练习2:选择性更新
实现一个用户信息更新功能,只更新非空字段。
练习3:批量操作
实现批量插入用户和批量删除用户的功能。
练习4:复杂动态SQL
结合多种动态SQL标签,实现一个复杂的报表查询功能。
运行示例程序
步骤1:导入项目
将chapter-07-dynamic-sql.json导入到IntelliJ IDEA中。
步骤2:配置数据库
确保MySQL数据库运行正常,执行init.sql创建测试数据。
步骤3:运行程序
运行DynamicSqlDemo.java,观察各种动态SQL标签的执行效果。
步骤4:查看日志
观察控制台输出的SQL语句,理解动态SQL的生成过程。
步骤5:运行测试
执行DynamicSqlTest.java中的测试用例,验证各种动态SQL功能。
本章小结
📖 本章小结
本章我们学习了 动态SQL 的相关内容,包括:
- 核心概念和基本原理
- 配置方法和实践技巧
- 应用场景和最佳实践
- 常见问题和解决方案
下一章节 🎉
恭喜完成本章学习!
你已经掌握了 动态SQL 的核心知识
第08章结果映射继续学习 MyBatis 的更多特性和应用⏱️预计 30 分钟📊中等难度开始学习 → ← 第06章 📚 课程目录 第08章 →