MyBatis 的动态 SQL
1. 动态 SQL 的定义
动态 SQL 是 MyBatis 的核心特性之一,它允许开发者根据运行时条件动态生成 SQL 语句。通过特殊的 XML 标签或注解语法,实现 SQL 的灵活拼接,避免在 Java 代码中手动拼接 SQL 字符串的复杂性和安全风险。
2. 核心作用
-
条件分支:根据参数值动态包含/排除 SQL 片段(如 WHERE 条件过滤)。
-
循环处理:遍历集合生成批量操作(如 IN 查询、批量插入)。
-
智能语法处理:自动去除多余的
AND
/OR
或逗号,保证 SQL 语法正确性。 -
防止 SQL 注入:通过参数占位符(
#{}
)预编译 SQL。
3. 常用标签详解
标签 | 作用 | 示例 |
---|---|---|
<if> | 条件判断,满足条件时包含 SQL 片段 | xml <if test="name != null">AND name = #{name}</if> |
<choose> | 多条件分支(类似 Java 的 switch-case ) | xml <choose><when test="age != null">AND age = #{age}</when><otherwise>AND age > 18</otherwise></choose> |
<foreach> | 遍历集合(如 List、Array) | xml <foreach item="id" collection="ids" open="(" separator="," close=")">#{id}</foreach> |
<where> | 智能处理 WHERE 子句,自动去除开头的 AND/OR | xml <where><if test="name != null">AND name = #{name}</if></where> |
<set> | 智能处理 UPDATE 语句,自动去除结尾逗号 | xml <set><if test="name != null">name = #{name},</if></set> |
<trim> | 自定义字符串修剪(灵活处理前后缀) | xml <trim prefix="WHERE" prefixOverrides="AND">...</trim> |
<bind> | 创建变量并绑定到上下文(用于模糊查询等) | xml <bind name="pattern" value="'%' + keyword + '%'" /> |
4. 典型应用场景
-
多条件组合查询
<select id="findUsers" resultType="User">SELECT * FROM user<where><if test="name != null">AND name LIKE #{name}</if><if test="minAge != null">AND age >= #{minAge}</if><if test="maxAge != null">AND age <= #{maxAge}</if></where> </select>
2.批量插入数据
<insert id="batchInsert">INSERT INTO user (name, age) VALUES<foreach item="user" collection="list" separator=",">(#{user.name}, #{user.age})</foreach> </insert>
3.动态更新字段
<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>
5. 优势分析
优势 | 说明 |
---|---|
代码简洁 | 避免 Java 代码中复杂的 StringBuilder 拼接 |
可维护性强 | SQL 与 Java 代码解耦,修改 SQL 无需重新编译 Java |
安全性高 | 自动使用 PreparedStatement 预编译,防止 SQL 注入 |
智能语法处理 | 自动处理逗号、AND/OR 等语法细节 |
灵活性高 | 支持复杂逻辑(嵌套条件、循环等) |
6. 底层实现原理
MyBatis 动态 SQL 基于 OGNL(Object-Graph Navigation Language)表达式 实现:
-
解析 XML 标签时,根据
test
属性中的 OGNL 表达式计算布尔值。 -
遍历 SQL 节点树,动态拼接符合条件的 SQL 片段。
-
最终生成标准的
PreparedStatement
可执行的 SQL。
7. 最佳实践
-
避免过度复杂:动态 SQL 嵌套不超过 3 层,否则可拆分为多个查询。
-
性能优化:使用
<where>
、<set>
减少无效字符处理开销。 -
参数校验:在 Java 层校验参数合法性,减少动态 SQL 判断分支。
-
注解替代 XML:简单场景可用
@SelectProvider
注解实现动态 SQL:@SelectProvider(type = UserSqlBuilder.class, method = "buildQuery") List<User> findUsers(UserQuery query);class UserSqlBuilder {public String buildQuery(UserQuery query) {return new SQL() {{SELECT("*");FROM("user");if (query.getName() != null) WHERE("name = #{name}");}}.toString();} }
8. 对比其他方案
方案 动态 SQL Java 拼接 SQL 可读性 高(结构化标签) 低(大量字符串拼接) 安全性 高(自动预编译) 低(易引发 SQL 注入) 维护成本 低(SQL 独立管理) 高(SQL 散落在 Java 代码中) 复杂逻辑支持 强(标签嵌套) 弱(需手动处理语法细节)