有关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
处理表达式计算,TrimSqlNode
、WhereSqlNode
等处理语法修剪;
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 studentWHERE stu_name like #{myvalue}
</select>
bind 元素属性如下:
- value:对应传入实体类的某个字段,可以进行字符串拼接等特殊处理。
- name:给对应参数取的别名。
以上代码中的“_parameter”代表传递进来的参数,它和通配符连接后,赋给了 pattern,然后就可以在 select 语句中使用这个变量进行模糊查询,不管是 MySQL 数据库还是 Oracle 数据库都可以使用这样的语句,提高了可移植性。
4.使用建议与注意事项
-
优先使用
<where>
和<set>
:避免手动添加1=1
或处理逗号,提高代码简洁性 -
模糊查询优化:使用
<bind>
标签统一处理模糊匹配,如<bind name="nameLike" value="'%' + name + '%'" />
,解决不同数据库的语法差异 -
性能考量:动态SQL可能影响执行计划优化,复杂条件建议结合索引设计使用