语句生成规 则
本章介绍如何使用 SQL 增强规则来简化动态 SQL 开发。相比 XML 标签,它们能更智能地处理空参数、连接符(AND/OR)和逗号分隔符。
| 规则 | 描述 |
|---|---|
@{and} / @{ifand} | 智能追加 AND 条件。 |
@{or} / @{ifor} | 智能追加 OR 条件。 |
@{in} / @{ifin} | 展开集合参数为 IN (v1, v2)。 |
@{set} / @{ifset} | 智能处理 UPDATE 语句的 SET 逗号。 |
@{if} / @{iftext} | 通用条件判断(解析 / 直出)。 |
@{text} | 原样输出文本片段(不解析参数)。 |
@{case} / @{when} / @{else} | 多路分支 (Switch / If-Else)。 |
@{macro} / @{ifmacro} | 引用预定义的 SQL 宏片段。 |
@{md5} | 计算参数 MD5 值。 |
@{uuid32} / @{uuid36} | 生成 UUID。 |
@{pairs} | 遍历 Map/List 生成参数模版。 |
AND、IFAND 规则
解决 WHERE 子句中动态拼接 AND 条件的问题。
@{and, sql片段}:当 SQL 片段中的参数非空(或包含${...}注入)时,自动追加该片段。@{ifand, OGNL条件, sql片段}:当 OGNL 条件表达式为真时,追加 SQL 片段(即使参数值为 null 也会保留)。
and 与 ifand 的区别
| 特性 | @{and, sql片段} | @{ifand, OGNL条件, sql片段} |
|---|---|---|
| 生效判断 | SQL 片段中的参数全部非 null 时生效 | OGNL 条件表达式为 true 时生效 |
| 空值参数 | 参数为 null 时整个片段被丢弃 | 条件满足时参数为 null 也会保留 |
| 典型用途 | 简洁写法,自动判空 | 需要自定义判断逻辑 |
特性说明
- 自动前缀去除:规则会自动处理
WHERE关键字和最前面多余的AND,例如WHERE @{and, AND x=1}会优化为WHERE x=1。 - 智能空值丢弃:
- 默认情况下,如果
@{and, col=:val}中的:val为空 (null),则整个片段会被丢弃。 - 例外:如果片段中包含
${...}动态注入,即使参数为空,该片段也会被强制保留(防止误删筛选逻辑)。
- 默认情况下,如果
示例
- 使用规则
- XML 对照
示例:status 必填,userId 选填。无需手动处理 WHERE/AND 连接符。
select * from users
where status = :status -- 固定条件
@{and, uid = :userId} -- 自动处理 AND 前缀
-- 仅当 userId 不为空且长度>0 时生效
@{ifand, userId != null && userId.length() > 0, uid = :userId}
select * from users
where status = ? and uid = ?
<select id="queryUser">
select * from users
where status = #{status}
<if test="userId != null">
and uid = #{userId}
</if>
</select>
- 自动补全 WHERE:若规则位于条件首位,会自动补全
WHERE关键字。 - 自动处理前缀:智能识别上下文,自动去除或添加必要的
AND。 - 自动降级:若前文是
OR,会自动适配。
OR、IFOR 规则
解决 WHERE 子句中动态拼接 OR 条件的问题。与 AND 规则对称。
@{or, sql片段}:当 SQL 片段中的参数非空时,自动追加该片段。@{ifor, OGNL条件, sql片段}:当 OGNL 条件为真时,追加 SQL 片段(即使参数为 null)。
- 使用规则
- XML 对照
示例:匹配用户名或邮箱。自动处理 OR 连接符。
select * from users
where username = :username
@{or, email = :email} -- 自动以 OR 连接
-- 仅当 email 包含 '@' 时生效
@{ifor, email != null && email.contains("@"), email = :email}
select * from users
where username = ? or email = ?
<select id="queryUser">
select * from users
where username = #{username}
<if test="email != null">
or email = #{email}
</if>
</select>
与 AND 规则一致,具备自动补全 WHERE 和自动修正连接符(OR)的能力。
IN、IFIN 规则
用于简化 IN 子句的拼接,自动展平集合参数。支持 List、数组(包括原始类型数组如 int[])。
@{in, :param}:自动将集合/数组参数展平为(?, ?, ...)。@{ifin, OGNL条件, :param}:当 OGNL 条件为真时生效。
- 使用规则
- XML 对照
示例:查询 ID 在列表中的用户。
select * from users
where status = :status
@{in, and id in :ids} -- 自动展开为 id in (?,?,?)
-- 仅当 ids 集合非空时生效
@{ifin, ids != null && ids.size() > 0, and id in :ids}
select * from users
where status = ? and id in (?, ?, ?)
<select id="queryUser">
select * from users
where status = #{status}
<if test="ids != null and ids.size() > 0">
and id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</select>
本规则不会自动补全 AND/OR 前缀,需在规则内手动写明(如示例中的 and id in ...)。
SET、IFSET 规则
专用于 UPDATE 语句,解决动态列更新时的逗号拼接问题。
@{set, sql片段}:参数非空时追加赋值,并自动管理逗号。与and/or不同,set允许参数值为 null。@{ifset, OGNL条件, sql片段}:当 OGNL 条件为真时生效。
- 使用规则
- XML 对照
示例:更新用户,status 为选填。
update users
set update_time = now() -- 固定列
@{set, status = :status} -- 自动处理逗号
-- 仅当 status != 'disabled' 时更新
@{ifset, status != null && status != 'disabled', status = :status}
where uid = :uid
update users
set update_time = now(), status = ?
where uid = ?
<update id="updateUser">
update users
set update_time = now()
<if test="status != null">
, status = #{status}
</if>
where uid = #{uid}
</update>
使用技巧
当在 @{set} 规则之间混合使用手动 SQL(例如 fixed_col = 1)时,无需手动添加逗号。
规则引擎会自动检测前文内容并智 能补充逗号。手动添加逗号反而可能在某些动态场景下导致语法错误。
update user
fixed_col = 1 -- 结尾不要加逗号
@{set, name = :name} -- 规则会自动处理前置逗号(生成 ", name = ?")
@{set, age = :age}
UPDATE tb_user SET
@{set, name = :name}, -- ❌ 规则无法删除身后的逗号
fixed_col = 123,
@{set, email = :email} -- ❌ 规则虽然不会添加新的逗号但也不会删除上一个条件中的逗号
WHERE id = :id
IF、IFTEXT 规则
通用条件判断规则。当 test 表达式为真时,将 content 包含在最终 SQL 中。
虽然功能类似,但两者对 content 内容的处理方式截然不同:
@{if, test, content}:智能解析。会对content进行完整解析,支持嵌套其他动态规则(如@{in})、参数占位符等。这是最常用的方式。@{iftext, test, content}:原生直出。不对content做任何解析,直接原样拼接到 SQL 中。用于注入特殊关键字或不支持参数化的语法片段。
- 使用规则
- XML 对照
select * from users where 1=1
-- 1. 普通 @{if}:支持解析内部的 @{in} 和参数 :name
@{if, hasName, and name = :name}
@{if, idList != null, and id in @{in, :idList}}
-- 2. 原生 @{iftext}:原样注入 SQL 片段(不解析参数)
@{iftext, status > 2, and age = 36 }
select * from users where 1=1
and name = ? -- @{if} 正常解析参数
and id in (?, ?) -- @{if} 允许嵌套 @{in}
and age = 36 -- @{iftext} 原样拼接
<select>
select * from users where 1=1
<!-- @{if} 对应标准 <if> -->
<if test="hasName">
and name = #{name}
</if>
<if test="idList != null">
and id in ...
</if>
<!-- @{iftext} 对应纯文本拼接 -->
<if test="status > 2">
and age = 36
</if>
</select>
TEXT 规则
原样输出文本片段,不解析其中的参数占位符或嵌套规则。
@{text, content}:将content原样拼接到 SQL 中。
select * from users where 1=1
@{text, and status = 'active'}
select * from users where 1=1
and status = 'active'
@{text, content}无条件输出,等价于@{iftext, , content}。@{iftext, test, content}需要test条件为真才输出。
CASE、WHEN、ELSE 规则
提供 SQL 生成阶段的分支逻辑,支持 Switch (值匹配) 和 If-Else (条件匹配) 两种模式。
- Switch 模式
- If-Else 模式
- XML 对照
值匹配:@{case} 第一个参数为变量。
select * from users where @{case, userType,
@{when, 'admin', role = 'administrator'},
@{when, 'manager', role = 'manager'},
@{else, role = 'visitor'}
}
select * from users
where role = 'administrator'
表达式匹配:@{case} 第一个参数为空。
select * from users where @{case, ,
@{when, userType == 'admin', role = 'administrator'},
@{when, userType == 'manager', role = 'manager'},
@{else, role = 'visitor'}
}
select * from users
where role = 'administrator'
<select id="queryUser">
select * from users where
<choose>
<when test="userType == 'admin'">role = 'administrator'</when>
<when test="userType == 'manager'">role = 'manager'</when>
<otherwise>role = 'visitor'</otherwise>
</choose>
</select>
- 模式切换:通过
@{case}第一个参数是否存在来切换 Switch / If-Else 模式。 - Switch 值匹配:
@{when, val}的val会通过 OGNL 求值后,与@{case, expr}的求值结果进行equals()比较。不同类型(如Integer(1)vs"1")会通过String.valueOf()回退匹配。 - Else:
@{else}必须写在最后。 - 默认值:若无匹配且无
else,输出空字符串。
MACRO、IFMACRO 规则
用于将预先定义的 SQL 片段(宏)包含进最终 SQL 中,类似于 XML 映射文件中的 <include> 标签。
@{macro, name}:引 用名称为name的 SQL 宏。@{ifmacro, test_expr, name}:当test_expr为真时,引用名称为name的 SQL 宏。
- 使用规则
- XML 对照
1. 注册宏 (Java)
// 通过 Configuration 注册
Configuration config = new Configuration();
config.addMacro("includeSeq", "and seq = :seq");
2. 引用宏 (SQL)
select * from users where
status = :status
@{macro, includeSeq} -- 引用宏
@{ifmacro, status > 2, includeSeq} -- 条件引用
select * from users
where status = ? and seq = ?
<!-- 定义 -->
<sql id="includeSeq">
and seq = #{seq}
</sql>
<!-- 引用 -->
<select id="queryUser">
select * from users where status = #{status}
<include refid="includeSeq"/>
<if test="status > 2">
<include refid="includeSeq"/>
</if>
</select>
引用一个不存在的 SQL 宏会导致执行报错。
在 Mapper XML 文件中使用 <sql id="xxx"> 定义的片段会自动注册为宏,命名规则为 namespace.id。
因此也可以通过 @{macro, namespace.sqlId} 在规则中引用 XML 定义的 SQL 片段。
MD5 规则
对参数值进行 MD5 哈希计算,并将结果作为 SQL 参数绑定。
@{md5, :param}:取:param参数的值,计算其 MD5 后绑定为?占位符。
参数必须使用 : 前缀引用(如 :loginPassword),否则不会被识别为参数引用。
- 使用规则
- 传统方式
示例:对密码进行 MD5 加密匹配。
select * from users
where account = :loginName
and password = @{md5, :loginPassword}
select * from users
where account = ? and password = ? -- 参数值为 MD5(loginPassword)
通常需要在 Java 层先计算好 MD5 再传入 SQL。
String md5Pwd = DigestUtils.md5Hex(password);
// 然后将 md5Pwd 传入 SQL 参数
UUID 规则
自动生成 UUID 并作为 SQL 参数。
@{uuid32}:生成 32 长度 UUID (无-分隔符)。@{uuid36}:生成 36 长度 UUID (带-分隔符)。
- 使用规则
- 传统方式
示例:插入时自动生成 ID。
insert into users (id, uid, name, time)
values (:id, @{uuid32}, :name, now());
insert into users (id, uid, name, time)
values (?, ?, ?, now()); -- 第二个参数为生成的 UUID
通常需要在 Java 层先生成 UUID。
String uid = UUID.randomUUID().toString().replace("-", "");
// 然后将 uid 传入 SQL 参数
PAIRS 规则
用于遍历集合(Map/List/Array)并按模版生成 SQL 片段。
@{pairs, :collection, template}:遍历:collection参数,对每个元素应用template。
模版变量(固定名称,不可配置):
| 变量 | Map 场景 | List/Array 场景 |
|---|---|---|
:k | Map 的 Key | 元素索引(字符串形式,如 "0"、"1") |
:v | Map 的 Value | 元素值 |
:i | 迭代序号(从 0 开始) | 迭代序号(从 0 开始) |
- 使用规则
- XML 对照
示例:将 Map 数据写入 Redis HASH 结构。
-- 假设参数 arg0 是一个 Map: {"field1": "val1", "field2": "val2"}
HSET myKey1 @{pairs, :arg0, :k :v}
HSET myKey1 field1 val1 field2 val2
MyBatis 的 <foreach> 标签类似:
HSET myKey1
<foreach collection="arg0" index="k" item="v" separator=" ">
#{k} #{v}
</foreach>