Java 8老系统SQL Agent实战:AI生成候选SQL,安全引擎拦截后再执行

Java 8老系统SQL Agent实战:AI生成候选SQL,安全引擎拦截后再执行 AI自然语言查库不能直接执行模型生成的SQL。本文详解SQL Agent安全引擎设计候选SQL审核、表白名单、敏感字段拦截、LIMIT强制补齐附带Java 8可运行Maven Lab代码。适合企业级AI数据查询安全接入场景。为什么要写这个系列做Java后端十年我接触过不少企业的核心系统。金融、电商、政务——行业不同但底层的现状惊人地相似生产系统还在Java 8框架停在Spring Boot 2.x甚至更早代码跑了很多年没人敢轻易动。去年开始几乎每个项目都在谈接AI。但真正动手的时候团队就卡住了。不是因为不懂大模型而是老系统本身接不住。JDK版本不够Spring AI引不进来依赖树牵一发动全身升级一个包怕带崩一片生产流量压着不敢拿主流程赌一个AI试点。更危险的是硬塞。我见过团队在老系统的Service层直接new一个HttpClient调模型APIPrompt拼在业务代码里超时没配、降级没有。模型响应慢的时候老系统的订单查询线程池被占满主流程跟着卡住。还有团队把用户手机号、身份证原样送进Prompt过了两个月才被安全审计发现。这种事见多了我就开始想一个问题老系统不具备直接接入AI的条件这是不是大多数企业的常态答案是肯定的。而且这不应该成为接不了AI的理由。核心思路其实就三条老系统少改 —— 不升级 JDK不引入 Spring AI 依赖 AI 能力旁路 —— 独立部署老系统通过 HTTP 或 MQ 调用 企业边界先行 —— 脱敏、审计、降级、幂等比模型调用更重要这个系列就是把这三条线展开成10讲。从AI Gateway到MCP工具中心从SQL Agent到RAG知识库从工单Agent到多Agent研发团队——每一讲都围绕同一个前提你的老系统还在跑Java 8你不能为了AI去赌它的稳定性。每讲配套一个可运行的Maven Lab不讲空架构不写Hello World。你跑得通Demo看得到边界拿得到代码。这就是我做这个系列的原因。这个系列帮你建立企业 AI 接入的架构意识。每个 Demo 都可独立运行每讲都配有边界设计和企业避坑。但意识不等于系统。当你真正要在自己的项目里落地时会发现10 个独立 Lab 的组件需要整合成一个完整的 AI 能力中心Stub 模型需要替换成真实模型调用权限、审计、脱敏需要从 Demo 级别升级到生产级别先把架构意识打好落地时才能走得更稳。Java 8老系统SQL Agent实战AI生成候选SQL安全引擎拦截后再执行企业里最容易被老板、业务方理解的AI场景之一是自然语言查数据。比如统计本月销售额最高的 10 个商品 查询本月退款订单数量听起来很简单用户说一句话 AI 生成 SQL 数据库执行 返回结果但真正放到企业系统里这条链路不能这么走。第1讲我们说Java 8老系统接AI要走旁路AI不直接改业务状态。第2讲我们说老系统能力要先包装成可授权、可审计、可脱敏的工具。这一讲继续往前走如果这个工具不是普通API而是”查数据库”边界要更严格。因为数据库查询一旦失控影响的不只是一个接口而是整片业务数据。AI 生成 DELETE 怎么办 AI 查敏感字段怎么办 AI 写了慢 SQL 怎么办 AI 越权查了别的租户怎么办 AI 把查询结果原样返回给模型怎么办所以这一讲的重点不是”让AI会写SQL”。重点是模型只能生成候选SQL候选SQL必须先经过安全引擎再决定能不能执行。自然语言查库最容易被误解很多人一听自然语言查库第一反应就是Text2SQL。这当然有价值但企业里真正卡住的地方通常不是模型能不能写出一条SQL而是这条SQL能不能被信任。在企业系统里模型输出不能被当成执行指令。它最多只是一个候选产物用户问题 ↓ 模型生成候选 SQL ↓ 安全引擎审核 ↓ 只读执行器执行 ↓ 结果摘要返回这和第2讲的工具授权思路一致。第2讲管的是API Tool第3讲管的是数据库查询ToolAI 能不能查这张表 能查哪些字段 查询范围和条数怎么限制 结果能不能原样返回边界没有变只是风险更集中。只读为什么仍然危险很多团队会觉得只要不让AI执行UPDATE、DELETE只允许SELECT风险就小了。这只说对了一半。只读不等于安全。一个企业可接受的查询工具至少要回答6个问题权限这个操作者是否有权查这类数据 租户查询是否被限制在当前 tenantId 参数时间范围、条数、聚合维度是否可控 字段是否包含手机号、证件号、内部备注等敏感字段 审计谁在什么时候问了什么生成了什么 SQL是否执行 返回值查询结果能否原样给模型或前端本讲Demo里SqlQueryRequest已经保留了tenantId和operatorId。它们不是装饰字段。真实项目里SQL Agent必须知道谁在查、代表哪个租户查、为什么查、查到了什么、有没有被安全策略拒绝。本讲Demo最终效果代码目录code/spring-ai-enterprise-lab/labs/chapter03-sql-agent运行.\compile-and-run.ps1跑完后能看到四类结果合法销售额查询放行。合法退款数量查询自动补LIMIT 50。删除数据意图被拦截。查询手机号被拦截。这四类结果想证明的是候选 SQL 不能直接执行 确定性安全代码必须挡在数据库前面端到端走一遍整体链路SqlQueryRequest ↓ SqlAgentService ↓ StubSqlGenerateService ↓ CandidateSql ↓ SqlSafetyEngine ↓ SqlSafetyDecision ↓ ReadOnlySqlExecutor ↓ SqlResultSummarizer ↓ SqlQueryResult第一步用户输入自然语言问题组装成SqlQueryRequestnew SqlQueryRequest(demo, u1001, question)第二步SqlAgentService调用StubSqlGenerateService生成候选SQL。注意这里叫CandidateSql——它不是最终SQL也不是可以立刻执行的SQL。第三步候选SQL进入安全引擎SqlSafetyDecision decision sqlSafetyEngine.inspect(candidateSql, SchemaSnapshot.orderReport());如果安全引擎拒绝链路直接结束不会进入执行器。第四步如果安全引擎放行进入只读执行器。这里执行的也不是原始候选SQL而是安全引擎处理后的safeSql。比如缺少LIMIT时安全引擎会自动补上LIMIT 50。第五步SqlResultSummarizer把查询结果变成摘要最终返回。这条链路的原则模型负责生成候选 SQL 安全引擎负责决定能不能执行 只读执行器只执行安全 SQL 摘要器只返回业务需要的结果代码结构src/main/java/com/ynzz/lab/chapter03 ├── Chapter03Demo.java ├── agent │ ├── StubSqlGenerateService │ ├── SqlAgentService │ ├── ReadOnlySqlExecutor │ └── SqlResultSummarizer ├── common │ ├── CandidateSql │ ├── SqlQueryRequest │ └── SqlQueryResult └── safety ├── SchemaSnapshot ├── SqlSafetyDecision └── SqlSafetyEngineagent负责生成、执行和总结。safety负责判断候选SQL是否安全。现在还没有接真实模型。StubSqlGenerateService先模拟模型生成候选SQL把注意力集中在安全引擎上。等安全链路跑通后再把Stub替换成真实模型调用。这个顺序很重要。如果一上来就接模型注意力会被prompt、模型效果、API Key牵走。但企业真正关心的是候选SQL到底能不能执行Schema白名单本讲模拟了一个报表表CREATE TABLE order_report ( id BIGINT PRIMARY KEY, order_id VARCHAR(64) NOT NULL, product_name VARCHAR(128) NOT NULL, customer_level VARCHAR(32) NOT NULL, order_month VARCHAR(16) NOT NULL, amount DECIMAL(12, 2) NOT NULL, status VARCHAR(32) NOT NULL, created_at TIMESTAMP NOT NULL );这张表故意不包含手机号、身份证、邮箱等敏感字段。重要设计原则SQL Agent 优先连接报表库、分析库、只读副本 不要让模型直接靠近生产业务库SchemaSnapshot维护了三类信息allowedTables、allowedColumns、sensitiveColumns。关键原则不要把完整数据库结构直接交给模型。模型只应该看到它能查询的部分。SQL安全引擎SqlSafetyEngine做了5件事1. 只允许 SELECT 2. 拦截 INSERT / UPDATE / DELETE / DROP / ALTER 3. 校验表白名单 4. 拦截敏感字段 5. 强制 LIMIT用户问”删除所有测试订单数据”Stub生成DELETE FROM order_report WHERE status TEST安全引擎返回{ blocked: true, blockReason: WRITE_OPERATION_NOT_ALLOWED, summary: SQL 已被安全引擎拦截未执行。 }运行效果合法查询放行返回摘要。没带LIMIT的退款查询安全引擎自动补上LIMIT 50。敏感字段查询”查询所有客户手机号”拦截blockReasonSENSITIVE_FIELD_NOT_ALLOWED。真实项目里还应该把这些信息写入审计记录原始问题、候选SQL、安全决策、执行状态、结果摘要、tenantId、operatorId。企业避坑不要让AI直连生产库— 至少用只读数据源最好从报表库、分析库开始。不要把完整Schema给模型— 模型只需要知道允许查询的表和字段。不要相信模型会生成安全SQL— 安全检查必须由确定性代码完成。不要忽略LIMIT— 没有限制的SELECT也可能拖垮系统。不要忘记租户和操作者上下文— 不同租户、角色允许看到的数据范围不同。不要把查询结果原样返回— 要做字段裁剪、敏感信息处理和摘要控制。不要缺审计— 自然语言查库必须记录完整调用链路。从 Demo 到落地还差什么本讲帮你验证了”候选 SQL 安全引擎”的核心思路但企业查库场景落地还差几步SQL Parser 替换字符串判断当前 Demo 用简单字符串匹配做安全检查。真实项目需要 SQL Parser如 JSqlParser支持复杂 SQL 语法——子查询、JOIN、嵌套 SELECT 等场景。多租户行级权限本讲在请求里带了 tenantId但安全引擎没有按租户过滤数据。真实项目里同一张表不同租户只能看到自己的数据行级权限必不可少。完整审计服务当前 Demo 只在控制台打印信息。真实项目需要审计服务异步写入、查询接口、告警规则、合规报表。查询资源配额不同租户的查询频率和返回行数应该有配额限制防止一个租户的查询拖垮整条链路。如果你正在推进企业 AI 查库能力落地后续会有完整版把这些组件整合成企业级 SQL Agent。小结模型只生成候选 SQL 确定性代码决定能不能执行 查询结果经过控制后再返回这就是企业Java系统接AI时必须补上的一层。