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调模型API,Prompt拼在业务代码里,超时没配、降级没有。模型响应慢的时候,老系统的订单查询线程池被占满,主流程跟着卡住。还有团队把用户手机号、身份证原样送进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 个商品
查询本月退款订单数量

听起来很简单:

sql 复制代码
用户说一句话
AI 生成 SQL
数据库执行
返回结果

但真正放到企业系统里,这条链路不能这么走。

第1讲我们说,Java 8老系统接AI要走旁路,AI不直接改业务状态。

第2讲我们说,老系统能力要先包装成可授权、可审计、可脱敏的工具。

这一讲继续往前走:如果这个工具不是普通API,而是"查数据库",边界要更严格。

因为数据库查询一旦失控,影响的不只是一个接口,而是整片业务数据。

sql 复制代码
AI 生成 DELETE 怎么办?
AI 查敏感字段怎么办?
AI 写了慢 SQL 怎么办?
AI 越权查了别的租户怎么办?
AI 把查询结果原样返回给模型怎么办?

所以这一讲的重点不是"让AI会写SQL"。

重点是:

模型只能生成候选SQL,候选SQL必须先经过安全引擎,再决定能不能执行。

自然语言查库最容易被误解

很多人一听自然语言查库,第一反应就是Text2SQL。

这当然有价值,但企业里真正卡住的地方通常不是模型能不能写出一条SQL,而是这条SQL能不能被信任。

在企业系统里,模型输出不能被当成执行指令。它最多只是一个候选产物:

markdown 复制代码
用户问题
        ↓
模型生成候选 SQL
        ↓
安全引擎审核
        ↓
只读执行器执行
        ↓
结果摘要返回

这和第2讲的工具授权思路一致。第2讲管的是API Tool,第3讲管的是数据库查询Tool:

复制代码
AI 能不能查这张表?
能查哪些字段?
查询范围和条数怎么限制?
结果能不能原样返回?

边界没有变,只是风险更集中。

只读为什么仍然危险

很多团队会觉得,只要不让AI执行UPDATEDELETE,只允许SELECT,风险就小了。

这只说对了一半。只读不等于安全。

一个企业可接受的查询工具,至少要回答6个问题:

sql 复制代码
权限:这个操作者是否有权查这类数据?
租户:查询是否被限制在当前 tenantId?
参数:时间范围、条数、聚合维度是否可控?
字段:是否包含手机号、证件号、内部备注等敏感字段?
审计:谁在什么时候问了什么,生成了什么 SQL,是否执行?
返回值:查询结果能否原样给模型或前端?

本讲Demo里,SqlQueryRequest已经保留了tenantIdoperatorId。它们不是装饰字段。

真实项目里,SQL Agent必须知道:谁在查、代表哪个租户查、为什么查、查到了什么、有没有被安全策略拒绝。

本讲Demo最终效果

代码目录:

bash 复制代码
code/spring-ai-enterprise-lab/labs/chapter03-sql-agent

运行:

arduino 复制代码
.\compile-and-run.ps1

跑完后能看到四类结果:

  • 合法销售额查询:放行。
  • 合法退款数量查询:自动补LIMIT 50
  • 删除数据意图:被拦截。
  • 查询手机号:被拦截。

这四类结果想证明的是:

sql 复制代码
候选 SQL 不能直接执行
确定性安全代码必须挡在数据库前面

端到端走一遍

整体链路:

markdown 复制代码
SqlQueryRequest
        ↓
SqlAgentService
        ↓
StubSqlGenerateService
        ↓
CandidateSql
        ↓
SqlSafetyEngine
        ↓
SqlSafetyDecision
        ↓
ReadOnlySqlExecutor
        ↓
SqlResultSummarizer
        ↓
SqlQueryResult

第一步 ,用户输入自然语言问题,组装成SqlQueryRequest

arduino 复制代码
new SqlQueryRequest("demo", "u1001", question)

第二步SqlAgentService调用StubSqlGenerateService生成候选SQL。

注意这里叫CandidateSql------它不是最终SQL,也不是可以立刻执行的SQL。

第三步,候选SQL进入安全引擎:

ini 复制代码
SqlSafetyDecision decision = sqlSafetyEngine.inspect(candidateSql, SchemaSnapshot.orderReport());

如果安全引擎拒绝,链路直接结束,不会进入执行器。

第四步 ,如果安全引擎放行,进入只读执行器。这里执行的也不是原始候选SQL,而是安全引擎处理后的safeSql

比如缺少LIMIT时,安全引擎会自动补上LIMIT 50

第五步SqlResultSummarizer把查询结果变成摘要,最终返回。

这条链路的原则:

sql 复制代码
模型负责生成候选 SQL
安全引擎负责决定能不能执行
只读执行器只执行安全 SQL
摘要器只返回业务需要的结果

代码结构

css 复制代码
src/main/java/com/ynzz/lab/chapter03
├── Chapter03Demo.java
├── agent
│   ├── StubSqlGenerateService
│   ├── SqlAgentService
│   ├── ReadOnlySqlExecutor
│   └── SqlResultSummarizer
├── common
│   ├── CandidateSql
│   ├── SqlQueryRequest
│   └── SqlQueryResult
└── safety
    ├── SchemaSnapshot
    ├── SqlSafetyDecision
    └── SqlSafetyEngine

agent负责生成、执行和总结。safety负责判断候选SQL是否安全。

现在还没有接真实模型。StubSqlGenerateService先模拟模型生成候选SQL,把注意力集中在安全引擎上。等安全链路跑通后,再把Stub替换成真实模型调用。

这个顺序很重要。如果一上来就接模型,注意力会被prompt、模型效果、API Key牵走。但企业真正关心的是:候选SQL到底能不能执行?

Schema白名单

本讲模拟了一个报表表:

sql 复制代码
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 复制代码
SQL Agent 优先连接报表库、分析库、只读副本
不要让模型直接靠近生产业务库

SchemaSnapshot维护了三类信息:allowedTablesallowedColumnssensitiveColumns

关键原则:

不要把完整数据库结构直接交给模型。模型只应该看到它能查询的部分。

SQL安全引擎

SqlSafetyEngine做了5件事:

sql 复制代码
1. 只允许 SELECT
2. 拦截 INSERT / UPDATE / DELETE / DROP / ALTER
3. 校验表白名单
4. 拦截敏感字段
5. 强制 LIMIT

用户问"删除所有测试订单数据",Stub生成:

sql 复制代码
DELETE FROM order_report WHERE status = 'TEST'

安全引擎返回:

json 复制代码
{
  "blocked": true,
  "blockReason": "WRITE_OPERATION_NOT_ALLOWED",
  "summary": "SQL 已被安全引擎拦截,未执行。"
}

运行效果

合法查询:放行,返回摘要。

没带LIMIT的退款查询 :安全引擎自动补上LIMIT 50

敏感字段查询 ("查询所有客户手机号"):拦截,blockReason=SENSITIVE_FIELD_NOT_ALLOWED

真实项目里还应该把这些信息写入审计记录:原始问题、候选SQL、安全决策、执行状态、结果摘要、tenantId、operatorId。

企业避坑

  1. 不要让AI直连生产库 --- 至少用只读数据源,最好从报表库、分析库开始。
  2. 不要把完整Schema给模型 --- 模型只需要知道允许查询的表和字段。
  3. 不要相信模型会生成安全SQL --- 安全检查必须由确定性代码完成。
  4. 不要忽略LIMIT --- 没有限制的SELECT也可能拖垮系统。
  5. 不要忘记租户和操作者上下文 --- 不同租户、角色允许看到的数据范围不同。
  6. 不要把查询结果原样返回 --- 要做字段裁剪、敏感信息处理和摘要控制。
  7. 不要缺审计 --- 自然语言查库必须记录完整调用链路。

从 Demo 到落地,还差什么

本讲帮你验证了"候选 SQL + 安全引擎"的核心思路,但企业查库场景落地还差几步:

SQL Parser 替换字符串判断:当前 Demo 用简单字符串匹配做安全检查。真实项目需要 SQL Parser(如 JSqlParser)支持复杂 SQL 语法------子查询、JOIN、嵌套 SELECT 等场景。

多租户行级权限:本讲在请求里带了 tenantId,但安全引擎没有按租户过滤数据。真实项目里同一张表,不同租户只能看到自己的数据,行级权限必不可少。

完整审计服务:当前 Demo 只在控制台打印信息。真实项目需要审计服务:异步写入、查询接口、告警规则、合规报表。

查询资源配额:不同租户的查询频率和返回行数应该有配额限制,防止一个租户的查询拖垮整条链路。

如果你正在推进企业 AI 查库能力落地,后续会有完整版把这些组件整合成企业级 SQL Agent。

小结

sql 复制代码
模型只生成候选 SQL
确定性代码决定能不能执行
查询结果经过控制后再返回

这就是企业Java系统接AI时必须补上的一层。

相关推荐
程序员海军16 分钟前
一个 AI 应用开发程序员的一天,都在屏幕前忙些什么?
前端·后端·程序员
不能放弃治疗24 分钟前
MCP
后端
江湖十年1 小时前
Go 语言中 YAML to JSON 踩坑笔记
后端·go·json
你驴我2 小时前
WhatsApp 消息撤回与编辑的幂等性设计实践
java·服务器·前端·后端·python
一缕清烟在人间2 小时前
HarmonyOS开发实战:小分享-TextEditPage文字编辑器——Header+TextArea+工具栏
后端·华为·harmonyos·鸿蒙
人间凡尔赛2 小时前
K8s 十年之变:从 Cloud Native 到 AI Native,2026 年后端架构的范式革命
后端·云原生·架构
程序员cxuan2 小时前
Opus 5 深夜炸场,价格还挺香。。。
人工智能·后端·程序员
北冥you鱼3 小时前
Go 语言新手扫盲:指针 * 和 & 使用场景详解
开发语言·后端·golang
青山木3 小时前
Hot 100 ---腐烂的橘子
java·数据结构·后端·算法·leetcode·广度优先
卷无止境3 小时前
Python的collections模块:那些被低估的"瑞士军刀"
后端·python