07-SqlBuilder六法
07-SqlBuilder六法SQL是怎么拼出来的SqlBuilder是元数据引擎的SQL工厂——580行九个public方法、六个private辅助。所有SQL拼接的规则都集中在这一个类里。这篇把九个build方法逐个拆开讲清每个方法的输入输出和它们共享的private辅助。文章目录07-SqlBuilder六法SQL是怎么拼出来的一、全方法地图二、buildSelect查询的主装配线三、appendColumns类型驱动的方言分支四、appendFrom三级降级五、buildInsert的两个版本六、buildUpdate的NULL语义七、appendJoinAndWhere vs appendWhereConditions一个逻辑两副面孔八、validateParams拼SQL前的最后一道闸九、BuiltSql不可变的结果对象源码browise-metadata/src/main/java/com/browise/ea/core/engine/SqlBuilder.java580行一、全方法地图public入口9个 ├── buildFromRawSql 原始SQL #{param}替换 ├── buildSelect 元数据SELECT含分页 ├── buildCount 元数据COUNT ├── buildCountCustom 原始SQL包COUNT ├── buildUpdate 元数据UPDATE ├── buildInsert 元数据INSERT ├── buildInsertBySearch INSERT跳空值版 ├── buildDelete 元数据DELETE ├── buildUpdateByFields / buildDeleteByFields 按字段值定位的改删 private辅助7个 ├── appendColumns SELECT列表达式类型→方言分支 ├── appendFrom FROM子句多表/主表/DUAL ├── appendJoinAndWhere 查询条件固定动态全运算符 ├── appendWhereConditions 改删条件简化版只 ├── appendWhereByFields 按字段值拼WHERE ├── typeConvertParam / typeConvertWhereParam 占位符类型转换 ├── validateParams 必填正则校验 └── extractParamNames #{}参数名提取 输出BuiltSql { sql, params } ——SQL文本按序参数值二、buildSelect查询的主装配线publicBuiltSqlbuildSelect(sqlMeta,fields,conditions,params,paginate,orderByColumn,minRow,maxRow){validateParams(fields,params);// ①必填正则前置校验StringBuildersqlnewStringBuilder();sql.append(select \n);appendColumns(sql,fields);// ②列表达式类型分支sql.append( from \n);appendFrom(sql,sqlMeta);// ③多表/主表/DUALsql.append( where 11 \n);// ④永真起点——后面全是 AND xxxListStringparamValuesappendJoinAndWhere(sql,conditions,params);// ⑤条件if(paginateminRow!nullmaxRow!null){strSqldialect.paginate(strSql,orderByColumn);// ⑥方言分页包装if(dialect.supportsOffset()){// ⑦两种分页参数序paramValues.add(maxRow-minRow1);// LIMIT sizeparamValues.add(minRow-1);// OFFSET start}else{paramValues.add(minRow);// ROWNUM minparamValues.add(maxRow);// ROWNUM max}}returnnewBuiltSql(strSql,paramValues);}where 11 是老把戏——后面每个条件都以 AND 开头拼不用判断第一个条件前要不要AND。⑦的两种参数序是分页bug的高发地LIMIT方言是 size,offset 顺序ROWNUM方言是 min,max 顺序——同一个BuiltSql.params列表尾部追加的两个值位置不同。KingBasePg方言就修过一次参数顺序bugBROWISE-STATUS记录。三、appendColumns类型驱动的方言分支privatevoidappendColumns(StringBuildersql,ListEaFieldMetafields){if(fieldsnull||fields.isEmpty()){sql.append(\t*);// 无字段配置 → SELECT *return;}for(EaFieldMetaf:fields){Stringexprf.getTableAlias().f.getColumnName();Stringtypef.getDataType();if(2.equals(type))// 日期sql.append(dialect.selectDateColumn(expr,alias));// to_char(x,yyyymmdd)elseif(3.equals(type))// 数字sql.append(dialect.selectNumberColumn(expr,alias));// to_char防精度丢失elseif(4.equals(type))// 日期时间sql.append(dialect.selectDateTimeColumn(expr,alias));// to_char完整格式elsesql.append(dialect.selectDefaultColumn(expr,alias));// 原样}}SqlBuilder自己不知道Oracle的to_char怎么写——它只知道日期类型要问方言。所有数据库差异封装在Dialect的八个方法里第09篇。这是典型的策略模式SqlBuilder是上下文Dialect是可替换策略。数字类型也走to_char是政务系统的现实——Oracle NUMBER经JDBC读出来可能变科学计数法尤其大额金额先在SQL层转字符串杜绝精度问题。四、appendFrom三级降级privatevoidappendFrom(StringBuildersql,EaSqlMetasqlMeta){ListEaTableMetatablessqlMeta.getTables();if(tables!null!tables.isEmpty()){// ①EA02配了多表 → 逗号分隔的隐式JOINfor(...)sql.append(t.getTableName() t.getTableAlias());}else{StringmainTablesqlMeta.getMainTable();if(mainTablenull||mainTable.isEmpty()){sql.append(\tDUAL);// ③啥都没有 → DUAL取序列/系统时间用}else{sql.append(\t).append(mainTable);// ②只有主表}}}DUAL降级是个贴心设计——一条只取序列或sysdate的SQLSELECT seq.nextval FROM DUAL不需要配任何表。五、buildInsert的两个版本// 标准版全字段出现空值插NULLpublicBuiltSqlbuildInsert(sqlMeta,fields,params){for(EaFieldMetaf:fields){cols.append(colName);if(有值){vals.append(typeConvertParam(type));paramValues.add(val);}else{vals.append(NULL);}// 占位符都不给直接NULL字面量}sqlinsert into T (c1,c2) values (?,NULL);}// 搜索版跳过空值字段全空抛异常publicBuiltSqlbuildInsertBySearch(sqlMeta,fields,params){for(EaFieldMetaf:fields){if(空值)continue;// 空字段不出现在列清单里}if(paramValues.isEmpty())thrownewEaException(PARAM_REQUIRED,无有效插入数据);sqlinsert into T (c1,c2) values (?,?);// 只有非空列}区别在空字段的语义标准版空值NULL写入覆盖式保存搜索版空值不插入保持数据库默认值/触发器赋值。表单没填的字段保持默认用搜索版没填就是清空用标准版。六、buildUpdate的NULL语义publicBuiltSqlbuildUpdate(sqlMeta,fields,conditions,whereParams,valueParams){sql.append(update ).append(resolveTableName(sqlMeta)).append(\n set \n);for(EaFieldMetaf:fields){StringvalvalueParams.get(f.getColumnName());if(非空){sql.append(colNametypeConvertParam(type));paramValues.add(val);}else{sql.append(colNameNULL);}// 前端传空 → 数据库置NULL}sql.append( where 11 \n);paramValues.addAll(appendWhereConditions(sql,conditions,whereParams));}UPDATE的空值NULL和INSERT标准版语义一致——这在配合 _t/_o 协议时有个讲究前端 collect 收集的 UPDATE 行只带修改过的字段没改的不在payload里。所以 valueParams 里没有的字段不会出现在SET里吗不对——buildUpdate遍历的是全部fields没传的字段全部SET NULL。这正是政务编辑表单的约定表单一次提交全量字段清空的字段就是要把库里清空。要只更新传了的字段用 buildUpdateByFields按originalParams里的字段值定位行。七、appendJoinAndWhere vs appendWhereConditions一个逻辑两副面孔查询用的 appendJoinAndWhere第08篇细讲条件规则支持全部运算符改删用的 appendWhereConditions 是简化版// 简化版固定值也参数化动态参数只支持if(c.getConditionType()1){sql.append(c.getColumnName()?);paramValues.add(c.getConditionValue());// 固定值也走占位符}else{sql.append(c.getColumnName()typeConvertWhereParam(type,));// 运算符写死}为什么改删的固定值也参数化这是安全修复CODE_REVIEW_REPORT 2.11的一部分——查询版固定值直接拼SQLAND u.STATUS1因为值来自管理员配置的元数据表改删影响数据固定值也走占位符多一层保险。八、validateParams拼SQL前的最后一道闸privatevoidvalidateParams(ListEaFieldMetafields,MapString,Stringparams){for(EaFieldMetaf:fields){if(!f.isRequired())continue;Stringkeyf.getFieldAlias()!null?f.getFieldAlias():f.getColumnName();Stringvalparams.get(key);if(valnull)valparams.get(f.getColumnName());// 别名查不到回退列名if(valnull||val.trim().isEmpty())thrownewEaException(PARAM_REQUIRED,f.getTitle());if(f.getRegexRule()!null!Pattern.matches(f.getRegexRule(),val))thrownewEaException(PARAM_FORMAT_ERROR,f.getTitle());}}必填校验和正则校验在buildSelect入口处——不是在Controller层做。理由元数据引擎的API/ea/query/{sqlId}是通用入口没有对应的Controller方法可以写校验——校验跟着元数据走谁调用都逃不过。九、BuiltSql不可变的结果对象publicstaticclassBuiltSql{privatefinalStringsql;privatefinalListStringparams;// 只有getter没有setter}拼好的SQL和参数绑在一起传递——SQL里的?个数必须和params.size()严格相等分开传容易出现错位。这个不变式由build方法的构造逻辑保证BuiltSql一旦生成就不可改。✅ 亮点580行SqlBuilder拆成9入口7辅助的完整地图重点讲类型驱动的方言分支、INSERT两版本的空值语义差异、改删固定值也参数化的安全修复、必填正则校验为什么放在引擎层。适合写SQL生成器的参考。扩展方向第08篇展开条件拼接规则、第09-10篇拆方言体系。

相关新闻

从“能用“到“好用“:Hy3 如何重新定义大模型的工程化落地

从“能用“到“好用“:Hy3 如何重新定义大模型的工程化落地

🌊 专注 AI 大模型与前沿科技深度解析,习惯从工程师视角拆解技术热点,让我们一起在技术浪潮中保持清醒与好奇 🚀从"能用"到"好用":Hy3 如何重新定义大模型的工程化落地 2026年的盛夏,大…

2026/8/25 7:22:12 阅读更多 →
构建OpenClaw智能体平台的三层诊断运维体系

构建OpenClaw智能体平台的三层诊断运维体系

1. 从“能用”到“会玩”:为什么你需要建立自己的OpenClaw运维逻辑最近在几个技术社群里,看到不少朋友在讨论OpenClaw的部署和报错。一个典型的场景是:照着教程,docker-compose up -d一把梭,服务跑起来了,感…

2026/8/25 7:22:12 阅读更多 →
多市场数据源各自为政,你的量化框架怎么统一的?QuantDash 统一 A股/美股/港股数据接口实战指南

多市场数据源各自为政,你的量化框架怎么统一的?QuantDash 统一 A股/美股/港股数据接口实战指南

📌 摘要 / 快速解答针对“多市场数据源各自为政,量化框架怎么统一”的问题——QuantDash 通过统一的代码后缀格式(.SH、.SZ、.US、.HK)和标准化的 Python SDK,将 A 股、美股、港股的数据接入抽象为同一套 API 范式。开…

2026/8/25 7:22:12 阅读更多 →

最新新闻

ESP32本地AI聊天机器人开发指南:从硬件选型到模型部署实战

ESP32本地AI聊天机器人开发指南:从硬件选型到模型部署实战

1. 项目概述:为什么是ESP32上的AI聊天机器人?最近几年,AI聊天机器人从云端“飞入寻常百姓家”,成了我们手机和电脑里的常客。但你想过没有,如果有一个完全属于你自己的、不依赖网络、能离线对话、还能被你随意“捏脸”…

2026/8/25 8:05:35 阅读更多 →
基于ESP32的离线AI聊天机器人:硬件选型、模型部署与实战优化

基于ESP32的离线AI聊天机器人:硬件选型、模型部署与实战优化

1. 项目概述:为什么选择ESP32打造AI聊天机器人?最近几年,AI聊天机器人从云端“飞入寻常百姓家”,成了很多开发者热衷折腾的新玩具。但你是否想过,让一个能和你对话的AI,脱离电脑和手机,变成一个…

2026/8/25 8:05:35 阅读更多 →
【Github(1)】windows安装及配置github

【Github(1)】windows安装及配置github

一、 安装git 参考该文章安装即可:windows安装git(全网最详细,保姆教程)-CSDN博客 注意在该文章的第四步中,需要勾选“(NEW!) Add a Git Bash Profile to Windows Terminal” 二、 配置git 1. 首先找到github网站&…

2026/8/25 8:05:35 阅读更多 →
拆解koa-react-full-example的Koa中间件栈:9层中间件如何撑起一个全栈SPA

拆解koa-react-full-example的Koa中间件栈:9层中间件如何撑起一个全栈SPA

拆解koa-react-full-example的Koa中间件栈:9层中间件如何撑起一个全栈SPA 【免费下载链接】koa-react-full-example Full example using Koa, React, Passport, Mongoose, Webpack, Mocha, Babel 项目地址: https://gitcode.com/gh_mirrors/ko/koa-react-full-exa…

2026/8/25 8:05:35 阅读更多 →
二叉树增删改查全解析:从C++代码到内存管理实战

二叉树增删改查全解析:从C++代码到内存管理实战

1. 项目概述:为什么二叉树是程序员的“基本功”?如果你写过代码,尤其是处理过稍微复杂一点的数据,比如文件目录、组织架构图,或者游戏里的技能树,那你大概率已经和二叉树打过交道了。它不像数组、链表那样直…

2026/8/25 8:05:35 阅读更多 →
Yocto2--编译树莓派内核(TODO)

Yocto2--编译树莓派内核(TODO)

(TODO) Phase 2 — 为 Pi 5 构建真实镜像(核心练习) 这才是真正"学会 Yocto"的阶段: Phase 3 — 自定义(真正理解 Yocto 的地方) 写自己的 layer 自定义 recipe(把 he…

2026/8/25 8:04:34 阅读更多 →

日新闻

洛谷 P7912:[CSP-J 2021 T4] 小熊的果篮 ← 双向链表

洛谷 P7912:[CSP-J 2021 T4] 小熊的果篮 ← 双向链表

【题目来源】 https://www.luogu.com.cn/problem/P7912 【题目描述】 小熊的水果店里摆放着一排 n 个水果。每个水果只可能是苹果或桔子,从左到右依次用正整数 1,2,…,n 编号。连续排在一起的同一种水果称为一个“块”。小熊要把这一排水果挑到若干个果篮里&#x…

2026/8/25 0:00:34 阅读更多 →
Transformers.js 网页端图像抠图实战:零后端 3 行代码返回透明 PNG

Transformers.js 网页端图像抠图实战:零后端 3 行代码返回透明 PNG

Transformers.js 网页端图像抠图实战:零后端 3 行代码返回透明 PNG 【免费下载链接】transformers.js State-of-the-art Machine Learning for the web. Run 🤗 Transformers directly in your browser, with no need for a server! 项目地址: https:/…

2026/8/25 0:00:34 阅读更多 →
数学建模竞赛论文写作指南:从模型构建到学术表达的核心技能

数学建模竞赛论文写作指南:从模型构建到学术表达的核心技能

1. 项目概述:从“会做”到“会写”的竞赛核心跃迁“全国大学生数学建模竞赛”,这个名字对理工科学生来说,分量极重。每年,无数团队在三天三夜的时间里,为一个开放性问题绞尽脑汁,从建立模型、求解算法到编程…

2026/8/25 0:00:34 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/25 3:38:12 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/25 3:38:18 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/25 3:38:23 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/24 20:22:44 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/23 12:10:44 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/24 11:20:22 阅读更多 →