08-条件拼接规则:固定值与动态参数
元数据引擎最核心的一段逻辑:EA05的条件元数据怎么变成WHERE子句。固定值始终拼接、动态参数空则跳过、括号支持嵌套分组、like运算符走方言模糊------这些规则决定了每一条查询的行为。这篇逐行拆appendJoinAndWhere,并用真实元数据走一遍完整拼接。
文章目录
源码:
browise-metadata/src/main/java/com/browise/ea/core/engine/SqlBuilder.javaL414-447
一、一段50行的方法,管着所有查询的WHERE
java
private List<String> appendJoinAndWhere(StringBuilder sql,
List<EaConditionMeta> conditions, Map<String, String> params) {
List<String> paramValues = new ArrayList<>();
if (conditions == null) return paramValues;
for (EaConditionMeta c : conditions) {
if (c.getConditionType() == 1) {
// ──────── 固定值分支 ────────
sql.append("\t ").append(c.getJoinOperator()).append(" ");
if (c.isHasOpenParen()) sql.append(" ( ");
sql.append(c.getConditionValue()); // 直接拼字面SQL片段
if (c.isHasCloseParen()) sql.append(" ) ");
} else {
// ──────── 动态参数分支 ────────
String paramName = c.getConditionValue();
String val = params.get(paramName);
if (val == null || val.isEmpty() || "null".equals(val))
continue; // 三态空全跳过
sql.append("\t ").append(c.getJoinOperator()).append(" ");
if (c.isHasOpenParen()) sql.append(" ( ");
String column = 有别名 ? alias.column : column;
String op = c.getOperatorSymbol();
sql.append(column).append(" ").append(op).append(" ");
sql.append(typeConvertWhereParam(c.getDataType(), op)); // 占位符
paramValues.add(val);
if (c.isHasCloseParen()) sql.append(" ) ");
}
}
return paramValues;
}
两个分支的行为差异全部源自一个字段:EAE013条件类型。
二、固定值分支:元数据就是SQL片段
conditionType==1 时,conditionValue 的内容原样拼进SQL:
EA05配置: EAE013=1, EAE015=AND, EAE014="u.STATUS = '1'"
拼出: AND u.STATUS = '1'
这不是漏洞吗?------值的来源是EA05元数据表,只有管理员能配置。这就是CODE_REVIEW修完后的边界:查询接口的固定值信任元数据(管理员权限),改删接口的固定值参数化(多一层保险)。
固定值的典型用途:
| 场景 | 配置 | 效果 |
|---|---|---|
| 只查在职 | u.STATUS='1' |
界面上永远看不到停保人员 |
| 逻辑删除过滤 | u.DELETED='0' |
软删数据对查询不可见 |
| 多表关联 | u.UNIT_ID=d.UNIT_ID |
隐式JOIN的关联条件(替代EA04) |
| 权限收窄 | u.ORG_CODE LIKE '4601%' |
只看本市数据(粗粒度) |
多表关联条件也走固定值------这是wiserise时代EA04(表间关联)在browise的简化归宿:关联就是一种固定WHERE。
三、动态参数分支:三态空判定与跳过
java
String val = params.get(paramName);
if (val == null || val.isEmpty() || "null".equals(val)) continue;
三种空全部跳过不是冗余------每一种都来自真实的调用方:
| 空的形态 | 来源 |
|---|---|
null |
前端没传这个参数(Map.get返回null) |
"" |
前端传了但输入框空着 |
"null" |
JSON序列化把null变成了字符串"null"------某些前端HTTP库的老毛病 |
跳过而不是报错------查询条件是可选的。用户没填姓名筛选,就不该有这个WHERE。这也是"零代码CommonQuery"能工作的基础:界面渲染了8个查询条件,用户只填2个,另外6个条件静默消失。
四、运算符与占位符的类型转换
java
sql.append(column).append(" ").append(op).append(" ");
sql.append(typeConvertWhereParam(c.getDataType(), op));
paramValues.add(val);
typeConvertWhereParam 按"运算符优先、类型其次"两级决策占位符形态:
java
private String typeConvertWhereParam(String dataType, String operator) {
if ("like".equals(operator))
return dialect.whereLikeParam(); // Oracle: '%'||?||'%' MySQL: CONCAT('%',?,'%')
if ("2".equals(dataType))
return dialect.whereDateParam(); // Oracle: to_date(?,'yyyy-mm-dd')
if ("3".equals(dataType))
return dialect.whereNumberParam(); // 通常就是?
if ("4".equals(dataType))
return dialect.whereDateTimeParam(); // to_date(?,'yyyy-mm-dd hh24:mi:ss')
return "?";
}
like的占位符不是一个孤零零的? ------Oracle的模糊匹配必须在SQL层做字符串拼接:'%'||?||'%'。参数只传"张",SQL层负责包%。
日期参数同理 ------前端传"2026-01-01"字符串,Oracle的DATE列不能直接等值比较,必须 to_date 包一层。参数值始终是纯字符串,类型转换全部发生在占位符表达式里------这是"字符串参数+方言占位符"协作的核心约定。
EaConditionMeta.getOperatorSymbol() 的运算符映射:
| EAE012 | 符号 | 说明 |
|---|---|---|
| 01 | = | 等值 |
| 02 | like | 模糊(走whereLikeParam) |
| 03/04 | > / < | 范围 |
| 05/06 | >= / <= | 闭区间 |
| 07 | <> / != | 排除 |
| 08-11 | in类 | 预留扩展 |
五、括号分组:EAE016/EAE017
java
if (c.isHasOpenParen()) sql.append(" ( ");
// ...本条件...
if (c.isHasCloseParen()) sql.append(" ) ");
配置"状态有效 且 (姓名含张 或 单位=XX局)":
| 行 | 列 | 运算符 | 连接符 | 左括号 | 右括号 | 值 |
|---|---|---|---|---|---|---|
| 1 | u.STATUS | = | AND | - | - | 固定 '1' |
| 2 | u.PSN_NAME | like | AND | 1 | - | 动态 psnName |
| 3 | u.UNIT_ID | = | OR | - | 1 | 动态 unitId |
拼出:
sql
WHERE 1=1
AND u.STATUS = '1'
AND ( u.PSN_NAME LIKE '%'||?||'%'
OR u.UNIT_ID = ? )
注意括号只包住单个条件的前后------跨多条件的括号组要靠连续多行的开闭括号组合出来。这是wiserise传下来的模型,够用但配置时容易错配------设计器界面上会可视化预览来防错。
六、一次完整拼接实录
元数据配置(承接第05篇的示例):
EA05三行(EAE001=SYS_USER_s):
行1: u.STATUS = AND 固定 '1'
行2: u.PSN_NAME like AND 动态 psnName
行3: u.UNIT_ID = AND 动态 unitId
调用 query("SYS_USER_s", {psnName:"张"})(unitId没传):
第一行(固定):
AND u.STATUS = '1'
第二行(动态,有值):
AND u.PSN_NAME LIKE '%'||?||'%' -- params: ["张"]
第三行(动态,null):
(continue跳过,什么都不拼)
最终BuiltSql:
sql
select
u.PSN_ID, u.PSN_NAME, u.PSN_SEX
from
SYS_USER u
where 1=1
AND u.STATUS = '1'
AND u.PSN_NAME LIKE '%'||?||'%'
-- params: ["张"]
-- 再由dialect.paginate包ROWNUM,追加minRow/maxRow两个参数
七、这套规则的边界
它不是通用查询构建器。 刻意不支持:
- 子查询条件(in后面跟select)------走rawSql通道
- OR连接的顶层条件组(首个条件不能是OR)------
where 1=1 AND...的起点写死了AND链 - 函数条件(
UPPER(name) like ?)------列名只支持裸列
这些限制换来的是:条件元数据模型足够简单(一行一个条件),管理界面可以表格化配置,普通业务人员能维护。复杂需求有rawSql逃生舱------两个通道覆盖全部场景,中间没有灰色地带。
✅ 亮点:50行appendJoinAndWhere逐行拆解------固定值/动态参数两分支的行为差异、三态空判定的来源、like与日期的方言占位符、括号分组配置法,最后用完整示例走一遍拼接实录。适合实现动态查询的参考。扩展方向:第09篇拆8方言体系、第11篇拆rawSql通道的#{}替换。