某些场景下, 需要批量执行某些 sql, 其中 sql 中表名或者字段名是变量。
原生sql中, 一般使用存储过程实现。 本工具为了满足此场景, 引入了 execBatch 批量操作。
说明
- SQL
-- 前置sql, 列出所有表名
show tables;
-- 单线程批量操作, 获取所有表的表结构
execBatch(show create table @tables);
```sql
-- 前置sql, 列出所有表名
show tables;
-- 多线程批量操作, 获取所有表的表结构, 如果单个任务失败不影响其他任务继续执行
execBatch(show create table @tables) ON concurrent=5 and ignore_error=true;
- 语法
execBatch(任意sql可以使用@变量名和${变量名}) [ON concurrent=5 and ignore_error=true and safe_params=(ip) and on_error_query=(select ‘${ip}查询失败’ as msg)];
- 参数 V:0.3.25
| 参数 | 必填 | 默认值 | 举例 | 说明 |
|---|---|---|---|---|
| concurrent | 否 | 1 | 5 | 并发数 |
| ignore_error | 否 | false | true | 是否忽略异常继续执行其他任务 |
| safe_params | 否 | - | ip | 允许不加引号引入sql的变量列表 |
| on_error_query | 否 | - | select ‘${ip}查询失败’ as msg | 子任务失败时时执行的sql |
| on_empty_query | 否 | - | select ‘${ip}查询为空’ as msg | 子任务为空时时执行的sql |
- 变量
允许使用 ${}, #{}, $@{} 变量, 它们都能引用变量, 但 @ 的个数决定变量被替换的层级(延迟层级), 详见下方「延迟变量(多级 @)的处理位置」; 引用时忽略外层单引号 ( ${} 与 ‘${}’ 等效 ).
默认除了safe_params以外, 所有的值都会强制嵌套外层单引号吗, 且内部的单引号会转移成两个单引号. (xxx = what’s 则 ${xxx} = ‘what’’s’)
-- xxx = what's 则 ${xxx} = 'what''s'
-- 推荐使用 '${xxx}' 表示变量名, 比如:
execBatch(select '${xxx}' as a)
-- 执行: select 'what''s' as a
-- 以下写法等效
execBatch(select ${xxx} as a)
execBatch(select '#{xxx}' as a)
-- 执行: select 'what''s' as a
-- 如果在 mybatis 环境下使用, ${} 和 #{} 会被 mybatis处理并报错, 可用 '$@{xxx}' 作延迟变量
execBatch(select '$@{xxx}' as a)
-- 执行: select 'what''s' as a
-- 以下写入有单引号问题
-- xxx = what's
execBatch(select '${xxx} you name' as a)
-- 执行: select 'what''s' as a
-- 错误: select ''what''s'you name' as a 引号不配对
-- safe_params 可以是一个或多个值
execBatch(select '${xxx} you name' as a) on safe_params=aaa
execBatch(select '${xxx} you name' as a) on safe_params=(aaa, bbb)
延迟变量(多级 @)的处理位置
execBatch / dynamicSourceQuery 的子 sql 会在「mybatis 接口参数 → execBatch 解析阶段 → 每行 data 阶段 → 内层嵌套命令 → DSQ 引擎」之间流转。
变量前每多一个 @,表示多延迟一层才被真正替换。这样设计是为了:
- 在 mybatis / http 接口环境下,
${x}会被 mybatis 抢先吃掉而报错,用$@{x}可逃过该层; - 在嵌套 execBatch 时,避免外层把内层才该用的变量提前替换掉。
两类变量都遵守「@ 个数 = 延迟层数」
| 写法 | 类型 | 含义 |
|---|---|---|
${x} / #{x} |
$ 变量(mybatis 风格) |
字符串值会被套引号;在 mybatis 接口最先被替换 |
$@{x} |
$ 变量,延迟 1 层 |
逃过 mybatis,到第 1 层才生效 |
$@@{x} |
$ 变量,延迟 2 层 |
逃过前两层 |
$@@@{x} |
$ 变量,延迟 3 层 |
用于 execBatch 内再嵌 execBatch/DSQ |
@item / @aa |
@ 变量(行变量) |
由 ReplaceAtValueUtil 替换,取自每行 data,不加引号 |
@$item |
@ 变量,延迟 1 层 |
第 1 层降为 @item |
@$$item |
@ 变量,延迟 2 层 |
第 2 层降为 @item |
源码只会逐级降级(
$@{→${、$@@{→$@{…;@$→@、@$$→@$…),不会跨级跳。写错@个数就会在错误的层级被替换(或残留不替换)。
各功能在哪些函数里处理延迟变量
| 层级 | 处理位置(源码) | 降级动作 | 实际替换(变量来源) |
|---|---|---|---|
| 第 0 层(入口) | dbutil-dao SqlAndParams:ReplaceSqlUtil.replaceMybatisParam(mybatis 参数) |
无(这就是最早吃 ${} 的地方) |
接口传入的 mybatis 参数 |
| 第 1 层 | SySqlExecBatchQueryExecutor.getBashSql(181-182 行)+ replaceMybatisParamNew(session.getQueryValue()) |
降一级 $@{→${、@$→@ |
session 变量(getQueryValue(),含延迟键) |
| 第 2 层 | SySqlExecBatchQueryExecutor.getAndReplaceSubSql(543-544 行)+ replaceMybatisParamNew(data) + ReplaceAtValueUtil.replaceStr(data) + session.getQueryValueIgnoreLaggedKeys() |
再降一级 | 每行 data(@item/@aa/${行字段})+ session(排除 sybn_lagged_*、sybn_last_* 等延迟变量) |
| 第 3 层 | 子 sql 被 SySqlEntityFactory.create 重新解析为内层 execBatch/dynamicSourceQuery,其各自的 getBashSql/getAndReplaceSubSql(或 SySqlDynamicSourceQuery*Engine.getAndReplaceSubSql,144/159-160 行)再降一级 |
再降一级 | 内层命令自己的 bash/item 阶段 |
dynamicSourceQuery(含SySqlDynamicSourceQuerySySqlEngine/DirectSqlEngine/RedisSqlEngine):只有一次降级(相当于第 1 层),之后用replaceAtStr(session.getQueryValueIgnoreLaggedKeys())替换,且排除延迟键。它不消费每行 data,所以 DSQ 里想引用@变量要在第 1 层前写好。cacheResult/peekResult自身不做$@{}降级,只用ReplaceSqlUtil.replaceMybatisParamNew(session.getQueryValue())+@list}处理(见SySqlCacheResultQueryExecutor136-142 行)。若 cacheResult 子 sql 内含 execBatch/DSQ,延迟变量交给内层命令处理。
session 的「延迟键」机制(lagged)
SySqlQuerySession.getQueryValueIgnoreLaggedKeys() 会剔除 sybn_lagged_*、sybn_last_*(@sybn_last_select@table 等)及 sybn_lagged_variables 列表中的键。
- 第 1 层
getBashSql用getQueryValue()(含这些键),第 2 层用getQueryValueIgnoreLaggedKeys()(不含)。 - 因此
@sybn_last_select@table这类中间结果表引用,要等到第 2 层(item 阶段)才被替换为真实数据,不会在第 1 层被提前吃掉。这正是SySqlExecBatchQueryExecutorTest#bug20250926Test验证的行为(execBatch 内union all select * from @sybn_last_select@table取到的是内层 b=3 而非外层 b=2)。
选型速查
| 需求 | 写法 |
|---|---|
| 变量来自 mybatis 接口参数 | 直接 ${x}(第 0 层替换) |
| 变量来自 session,在 execBatch 解析阶段就要用 | ${x}(第 1 层) |
| 变量想延迟到每行 data 才替换(如按行表名) | @item/@aa(推荐)或 ${x}/$@{x}(需 session 无同名 key,否则第 1 层被 session 顶掉);要强制延迟到第 2 层用 $@@{x} |
| 变量想延迟到嵌套内层命令才替换 | $@@{x} / $@@@{x}(按嵌套深度加 @) |
按行 @item/@aa 想延迟一级 |
写 @$item / @$aa |
注意:
$${}是另一种「二级变量」(来自ServerSettingUtil的全局配置,见ReplaceSqlUtil),与$@{}延迟语法无关,勿混淆。
相关测试类
sybn-core/.../executor/SySqlExecBatchQueryExecutorTest#bug20250926Test:验证@sybn_last_select@table在 execBatch 内延迟到 item 阶段取正确值。sybn-core/.../finder/ConvertSySqlExecBatchQueryTest:验证@aa(每行 data)与#{t}(session)在 execBatch 中替换。sybn-core/.../part/util/ReplaceSqlUtilTest:$${}全局配置变量替换。sybn-jdbc-driver/.../dql/SySqlDynamicSourceQueryTest:验证@host/@port在 DSQ 的 on 配置中替换。- 当前没有专门覆盖
$@{x}/$@@{x}字符串降级的单元测试,改动降级逻辑时建议补一个回归测试。