前阵子有个做后端管理的同事跟我吐槽:新来的实习生问“咱们库到底有哪些表”,翻了半天文档也没理清楚,最后只能把Navicat里的表结构截图一张张发过去。我当时就在想,与其给人一张张截图表名,不如让数据库自己开口说话。后来我抽空做了个小工具,用Java写调用逻辑,本地跑Ollama起一个大模型服务,用户直接输入“数据库里有哪些表”,模型理解意图后,由Java后台去查PostgreSQL的元数据,把所有数据表名称列出来。整个过程数据不出内网,模型本地推理,Java侧只执行白名单里的查询SQL,安全性和可控性都有保障。今天就把完整的实现思路、代码和踩过的坑都整理出来,给同样想做数据库自然语言查询入口的同学一个参考。
1. 为什么要把Ollama、Java和PostgreSQL串起来:一个“问表”工具的真实需求
1.1 三种“查表名”的做法,反差在哪里
先说清楚一个事实:如果想查PostgreSQL里有哪些数据表名称,本质上是一条SQL就能解决的问题。比如:
SELECT tablename FROM pg_tables WHERE schemaname = 'public';但问题从来不在SQL本身,而在“谁有资格执行这条SQL”。我把常见的查表方式拉了一个对比,你会发现它们是递进关系,不是替代关系。
| 查表方式 | 上手门槛 | 适合人群 | 典型缺点 |
|---|---|---|---|
| 数据库客户端直接执行SQL(pgAdmin、DBeaver) | 低,但必须会SQL | DBA、后端开发 | 业务人员不会用,新员工不知道该查哪张表 |
| Java JDBC / DatabaseMetaData 编程查询 | 中,需要会写代码 | 开发人员,做成内部接口 | 依然是面向程序员的工具,非技术人员无法自助使用 |
| 自然语言 + Ollama + JDBC | 模型层零门槛,Java层封装 | 所有角色,包括产品、运营、新入职同学 | 需要维护本地模型环境,设计Prompt和安全边界 |
我做这个工具的初衷,就是想让第三种方式在内部落地。输入“帮我看看订单相关的表”,系统返回订单域的几张表名;输入“这个库有哪些表”,系统把全部表名列出来。自然语言入口的价值不在于“替代SQL”,而在于把一个需要技术背景的操作,变成了一个所有人都能对话的动作。
1.2 技术栈选择的理由:私有化、Java存量、PG元数据规范
为什么偏偏是Ollama、Java、PostgreSQL这个组合?三个理由,每一个都是实际业务场景逼出来的。
第一,数据安全限制。表名听起来没什么,但实际上表名会暴露业务结构:users、orders、payment_records,一路看下来你的系统架构就透明了。很多企业内部明确禁止把任何数据相关的内容发送到外部API,所以必须走本地推理模型。Ollama在这类场景里确实方便:一条命令起服务,HTTP API风格接近OpenAI,Java调用成本极低,还天然支持离线部署。我在内网服务器上部署一次之后,局域网内所有机器都能通过一个内网地址访问,体验非常接近云端API。
第二,Java是存量技术栈。公司内部管理后台、工单系统、数据平台基本是Java/Spring Boot,把“自然语言查表”的能力做成一个Java组件塞进现有系统,比引入一套独立技术栈要平滑得多。Java 11之后自带的HttpClient类就能完成Ollama API的调用,连额外的HTTP客户端依赖都不用加,这对轻量工具来说很友好。
第三,PostgreSQL的元数据查询有标准答案。information_schema是SQL标准定义的元数据视图集合,PostgreSQL对它的支持很完整。查表名、查字段、查约束,都能在information_schema里找到对应视图,写出来的SQL语义清晰,基本不依赖数据库私有语法。这给后续扩展“查字段”“查索引”留了很大的余地。
1.3 整条链路的流程概述
为了后面看不乱,我先给整个链路搭个框架。整个流程可以拆成四步:
- 用户输入自然语言,例如“数据库里有哪些表”。
- Java调用本地Ollama服务,传入设计好的Prompt,让模型输出一个结构化的意图JSON。
- Java解析这个JSON,通过白名单判断,只放行预先定义好的操作,比如list_tables。
- Java使用JDBC连接PostgreSQL,执行写死的元数据查询SQL,把表名列表返回给用户。
这里有一个设计上的关键点:Ollama只做“意图识别”,不生成SQL。市面上很多方案会让大模型直接生成SQL然后交给数据库执行,我明确不建议这么做,原因在后面“安全边界”部分展开。LLM做翻译,Java做控制,数据库做查询,各司其职,整条链路才不会失控。
2. 环境搭建:Ollama模型准备、PostgreSQL初始化、Java依赖配置
2.1 Ollama本地部署的完整实操
Ollama的安装本身不复杂,真正麻烦的是模型下载。我先说安装:Windows直接下载安装包双击;macOS同理;Linux服务器上用官方脚本一条命令,或者下载二进制包手动解压到/usr/local/bin。装完启动服务,默认监听11434端口。
模型选择上,我推荐先从qwen2.5:7b起步。查表名这个任务本质上是一个意图分类任务,对模型的推理能力要求不高,7B参数级别完全够用,8GB左右显存就能跑起来。如果机器显存很紧张,可以换3B/4B级别的小模型;如果对输出稳定性有更高要求,且机器配置允许,再上14B。我实测下来,7B模型配合好的Prompt,意图识别准确率已经很高。
模型下载慢的问题,可能是大家问得最多的。我的经验是三种方式组合使用:一是优先找国内能访问的镜像站拉取,把模型文件下载下来再通过ollama create导入,或者找离线安装包在局域网内分发;二是把下载任务放在网络空闲时段执行,不要在工作时间盯着进度条焦虑;三是如果只是做开发验证,先用体积最小的模型版本跑通链路,再换正式的7B模型。这里顺便提醒一句:模型下载失败时,别反复重试同一个源,先看是不是磁盘空间不足。Ollama的模型文件动辄几个GB,df -h看一眼往往比排查网络更有效。
模型默认存储路径也值得提前规划。Linux下Ollama默认存在/usr/share/ollama/.ollama/models或当前用户目录下,系统盘空间不够时非常被动。建议提前设置环境变量OLLAMA_MODELS指向数据盘,比如:
export OLLAMA_MODELS=/data/ollama/models然后重启Ollama服务,再重新拉模型。Windows下可以在系统环境变量里同样设置这个变量,路径指向D盘或E盘的某个目录。这个动作最好在一开始就做,否则模型已经下好了再迁移,要重新拷好几GB文件。
安装完成后用一行命令验证服务状态:
curl http://localhost:11434/api/tags能返回一个包含已安装模型的JSON数组,说明Ollama服务正常,可以进入下一步。
2.2 PostgreSQL版本选择和测试库准备
PostgreSQL版本选择,很多人会纠结。我的建议很直接:如果公司有生产环境就跟随生产环境的大版本,省心;如果是从零开始,直接用16或以上版本。16版本在查询优化和并发控制上做了不少改进,而且作为较新的稳定版本,社区资料和工具链支持都很成熟。PostgreSQL的升级路径也相对平滑,不用太担心版本绑死的问题。
安装方式,本地开发我强烈推荐Docker,干净、可变现、删了重来没负担:
docker run --name pg-test -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:16如果是Ubuntu物理机,用apt安装也一样,只是要记得设置postgres用户的密码,并确认pg_hba.conf的认证方式和监听地址。默认情况下PostgreSQL只监听localhost,局域网访问需要改listen_addresses,本地测试不用动。
装好之后建一个测试库,再随便造几张表:
CREATE DATABASE testdb; \c testdb CREATE TABLE customers (id serial PRIMARY KEY, name text); CREATE TABLE orders (id serial PRIMARY KEY, customer_id int, amount numeric); CREATE TABLE products (id serial PRIMARY KEY, title text, price numeric);这几张表名都设计成业务含义明确的词,方便后面验证Ollama的意图识别效果。实际上测试模型的时候,表名最好也准备一些带业务色彩的,比如vip_user_info、tmp_order_backup这种,看看模型会不会被干扰。
2.3 Java Maven工程与JDBC最小连通验证
Java侧我用的是Maven工程,核心依赖只有三个:
<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.7.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.17.1</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.1.0</version> </dependency>postgresql是JDBC驱动,jackson-databind负责解析Ollama返回的JSON,HikariCP是连接池。先别急着接Ollama,第一步先在Java里把PostgreSQL的表名查出来,确认数据库链路通,再做模型接入,这样排查问题能少一半的干扰。
最小验证代码很简单:
String url = "jdbc:postgresql://localhost:5432/testdb"; String user = "postgres"; String password = "postgres"; try (Connection conn = DriverManager.getConnection(url, user, password)) { String sql = """ SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name """; try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { while (rs.next()) { System.out.println(rs.getString(1)); } } }这里我直接用了information_schema而不是pg_tables,原因后面细说。跑通这一步,数据库侧的基础就算打好了。
3. 核心实现:Java调用Ollama理解意图,再查询PostgreSQL所有表名
3.1 Ollama API调用逻辑:/api/chat接口与Java HttpClient封装
Ollama对外提供的REST API里,有两个接口经常被用到:/api/generate和/api/chat。前者是纯文本补全,适合“给一段文本让模型续写”的场景;后者是对话式接口,接收messages数组,每个元素带role和content,适合做意图识别这样的多轮任务。我选/api/chat,因为意图识别本身就是一个“系统提示 + 用户提问”的对话结构。
Java 11之后自带的java.net.http.HttpClient完全可以胜任调用任务,不需要额外引入OkHttp或RestTemplate。封装一个OllamaClient很简单:
public class OllamaClient { private final String baseUrl; private final HttpClient httpClient; private final ObjectMapper objectMapper = new ObjectMapper(); public OllamaClient(String baseUrl) { this.baseUrl = baseUrl; this.httpClient = HttpClient.newHttpClient(); } public String chat(String model, String systemPrompt, String userMessage) throws IOException, InterruptedException { Map<String, Object> payload = new HashMap<>(); payload.put("model", model); payload.put("stream", false); Map<String, String> systemMsg = new HashMap<>(); systemMsg.put("role", "system"); systemMsg.put("content", systemPrompt); Map<String, String> userMsg = new HashMap<>(); userMsg.put("role", "user"); userMsg.put("content", userMessage); List<Map<String, String>> messages = new ArrayList<>(); messages.add(systemMsg); messages.add(userMsg); payload.put("messages", messages); Map<String, Object> options = new HashMap<>(); options.put("temperature", 0.1); payload.put("options", options); String requestBody = objectMapper.writeValueAsString(payload); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "/api/chat")) .header("Content-Type", "application/json") .POST(BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) .build(); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if (response.statusCode() != 200) { throw new RuntimeException("Ollama API error: " + response.statusCode() + ", body: " + response.body()); } JsonNode root = objectMapper.readTree(response.body()); return root.path("message").path("content").asText(); } }几个细节请注意。第一,stream必须显式设为false,否则Ollama会返回流式chunk,Java侧处理起来麻烦很多。第二,temperature设置成0.1,让模型输出尽可能确定性,意图识别这种任务不需要创造性,越稳定越好。第三,用Jackson的writeValueAsString来构建请求体,而不是手动拼字符串,可以避免中文和特殊字符转义问题。
3.2 Prompt设计:把自然语言收敛成结构化意图
模型输出稳不稳定,七成看Prompt。我的做法是让模型输出一个固定结构的JSON,而不是直接输出SQL。设计如下:
system: 你是一个数据库元数据查询助手。用户会用自然语言询问数据库结构相关问题。 你的任务:识别用户的意图,只输出一个JSON对象,不要输出任何其他文字、解释或代码。 可用意图枚举: - list_tables:用户想查看数据表名称,例如“有哪些表”“列出所有表”“数据库里都有什么表”。 输出格式:{"action": "list_tables", "schema": "public"} 示例: 用户:数据库里有哪些表? 输出:{"action": "list_tables", "schema": "public"} 用户:请帮我列出这个库的全部数据表名称 输出:{"action": "list_tables", "schema": "public"} 如果无法识别,输出:{"action": "unknown", "schema": "public"}这个设计里有几个值得说道的点。首先,严格要求“只输出JSON对象”,杜绝了“好的,数据库中的表有:”这类废话输出,Java侧解析失败率大幅下降。其次,few-shot示例给了两个不同的问法,让模型理解“有哪些表”和“列出全部表名”是同一个意图,这比只给一个示例泛化能力强得多。第三,schema字段也给默认值public,为后续扩展多schema场景留了口子。
另外,Ollama支持在请求体里加format: "json"参数,强制模型输出JSON结构,这是2024年之后新版本才支持的特性,值得打开,能进一步约束输出。在调用时给payload加一行就行了。
3.3 查询PostgreSQL所有数据表名称的SQL:三种写法与选型对比
PostgreSQL里查表名,至少有三种写法,我做个对比,方便你按场景选择:
| 写法 | SQL示例 | 特点 |
|---|---|---|
| pg_tables | SELECT tablename FROM pg_tables WHERE schemaname = 'public' | PostgreSQL私有系统视图,写法最简洁,但不跨库 |
| information_schema.tables | SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' | SQL标准元数据视图,语义清晰,跨数据库兼容性好,推荐 |
| pg_class + pg_namespace | SELECT c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relkind = 'r' | 直接查底层系统表,性能最优,但可读性差,适合表数量极大时的性能优化 |
我推荐information_schema方案,原因有三。一是它是SQL标准的一部分,在MySQL、PostgreSQL和一些国产数据库上都能跑,将来切换数据库成本低。二是它有明确的table_type字段,可以精确过滤出“BASE TABLE”,也就是普通表,把视图排除在外。三是字段名table_name、table_schema语义一目了然,后面接ODBC、接BI工具都方便。
如果业务里存在多个schema,需要把所有非系统schema下的表都列出来,SQL改成这样:
SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema') AND table_type = 'BASE TABLE' ORDER BY table_schema, table_name;这个写法会同时输出schema名和表名,更贴近企业库“一堆schema各管一堆表”的真实结构。内网管理工具里我建议默认用这个,而不仅仅是查public。不过标题这个场景简化一点,只查public也没问题。
3.4 完整可运行的Java代码
把上面的模块串起来,主流程代码长这样。先定义一个意图类,JDK 16及以上可以用record:
public record Intent(String action, String schema) {}然后是建表查询类:
public class TableLister { public List<String> listTables(Connection conn, String schema) throws SQLException { List<String> tables = new ArrayList<>(); String sql = """ SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND table_type = 'BASE TABLE' ORDER BY table_name """; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, schema); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { tables.add(rs.getString(1)); } } } return tables; } }最后是组装主流程:
public class Main { public static void main(String[] args) throws Exception { String userInput = args.length > 0 ? args[0] : "数据库里有哪些表"; String model = "qwen2.5:7b"; String systemPrompt = "你是一个数据库元数据查询助手..." // 完整Prompt见上文 OllamaClient ollama = new OllamaClient("http://localhost:11434"); String modelOutput = ollama.chat(model, systemPrompt, userInput); ObjectMapper mapper = new ObjectMapper(); Intent intent; try { intent = mapper.readValue(modelOutput, Intent.class); } catch (JsonProcessingException e) { throw new RuntimeException("模型输出不是合法JSON: " + modelOutput, e); } if (!"list_tables".equals(intent.action())) { System.out.println("抱歉,我只支持查询数据表名称。"); return; } String url = "jdbc:postgresql://localhost:5432/testdb"; try (Connection conn = DriverManager.getConnection(url, "postgres", "postgres")) { TableLister lister = new TableLister(); List<String> tables = lister.listTables(conn, intent.schema()); System.out.println("当前数据库共有 " + tables.size() + " 张表:"); tables.forEach(System.out::println); } } }运行效果就是一行对话:
输入:数据库里有哪些表 输出:当前数据库共有 3 张表: customers orders products到这一步,一个最基础的“Java + Ollama + PostgreSQL查表名”链路已经通了。但“能跑”和“能稳定用”之间还有一段距离,下面这部分才是真正的实战干货。
4. 实测踩坑:从demo到稳定可用的五个细节
4.1 为啥查出来多了很多系统表:schema过滤的必要性
第一次跑通的时候,我印象最深的一个坑是:表名列表里全是pg_开头的系统表,几百条刷屏。原因很简单,pg_tables和information_schema.tables默认会把数据库内部的系统表也暴露出来。
PostgreSQL内置了pg_catalog和information_schema两个系统schema,里面存的是数据库自己的元数据表。如果不加过滤条件,查出来的列表几百行,业务表淹没在系统表里,毫无可用性。解决办法就是前面的SQL里写死的两个条件:table_schema = 'public'或者table_schema NOT IN ('pg_catalog', 'information_schema')。
这个坑看着不起眼,但实际遇到时特别迷惑,因为系统表名大多以pg_或sql_开头,一眼看上去还以为是数据被污染了。我的建议是:从第一版代码开始,就把schema过滤条件写进SQL常量里,不要等到用户反馈“表名列表不对”再去排查。
4.2 模型输出偶尔不乖:temperature参数、JSON解析、重试策略
Ollama默认的temperature是0.8,这个参数下模型“更有创造力”,容易在JSON前后加解释文字,甚至输出“好的,下面是你要的表名列表”这种废话。意图识别任务是确定性的分类任务,不需要创造力,必须把temperature降到0.1左右。
即使temperature很低,模型依然有小概率输出不合法JSON,比如单引号代替双引号、末尾多一个逗号。我实测处理办法是加一道“解析失败重试”逻辑:第一次模型输出解析失败,把失败原因拼进对话重新请求一次,让模型修正。
String modelOutput = null; for (int attempt = 0; attempt < 2; attempt++) { String output = ollama.chat(model, systemPrompt, userInput); try { intent = mapper.readValue(output, Intent.class); break; } catch (JsonProcessingException e) { if (attempt == 1) { throw new RuntimeException("模型连续两次输出非法JSON: " + output, e); } userInput = userInput + "\n注意:你刚才的输出不是合法JSON,请重新只输出JSON对象。"; } }如果Ollama版本支持,强烈建议在请求里加上format: "json"。这个参数会约束解码器只生成JSON结构,我在qwen2.5上开了之后,解析失败率基本归零。
4.3 JDBC连接不能每次新建:HikariCP连接池配置
我上面给的示例代码用DriverManager.getConnection,每次查询都新建连接,这在命令行demo里没问题,但一旦接到HTTP接口上,并发一高就会暴露两个问题:PostgreSQL每建一次连接都要完成TCP握手和身份认证,耗时几十毫秒到几百毫秒不等;连接数没有上限,压力大的时候能把数据库的连接数打爆。
换成HikariCP是标准做法,配置非常轻量:
HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:postgresql://localhost:5432/testdb"); config.setUsername("postgres"); config.setPassword("postgres"); config.setMaximumPoolSize(5); config.setMinimumIdle(2); config.setConnectionTimeout(3000); DataSource dataSource = new HikariDataSource(config);然后所有查询都从dataSource.getConnection()拿连接,用完即还。maximumPoolSize设置成5就够内部工具用了,不需要贪多。这个优化做完,接口在并发查询下依然稳定,不需要其他花活。
4.4 安全边界:意图白名单 + 最小权限只读账号
这一点是我最想强调的。很多人做LLM + 数据库的第一反应是让模型直接生成SQL,然后传给数据库执行。我强烈不建议这么做,至少在内部工具阶段不要做。原因有两个:第一,模型生成的SQL很可能有语法错误,SELECT拼错一个字母,整个查询就崩了;第二,如果Prompt被绕过,模型完全可能生成DROP TABLE、DELETE FROM之类的危险语句,而数据库并不知道这句话来自“意图识别失败”还是“恶意构造”。
我采用的安全策略是“意图白名单 + 最小权限账号”。意图白名单的意思是:Ollama模型允许输出的action只有我们预先定义的那几个枚举值,比如list_tables、list_columns。Java侧拿到模型输出之后,必须校验action在枚举集合内,否则直接拒绝执行。模型永远没有机会生成SQL,SQL都是我们自己写死、参数化绑定的。
最小权限账号的意思是:不要用postgres超级用户连接数据库,单独建一个只读账号:
CREATE ROLE meta_reader LOGIN PASSWORD 'readonly'; GRANT CONNECT ON DATABASE testdb TO meta_reader; GRANT USAGE ON SCHEMA public TO meta_reader; GRANT SELECT ON ALL TABLES IN SCHEMA public TO meta_reader;以后JDBC的连接串里就用这个只读账号。就算将来安全边界被突破,能执行的最多也只是SELECT,不会造成数据破坏。这个成本极低,但价值极高,内部工具至少要做到这一层。
4.5 模型未加载时的兜底处理
Ollama服务起来了,并不代表模型已经下载好了。调用/api/chat时如果模型不存在,Ollama默认会返回404模型不存在的错误。第一次部署时我就在这个环节卡了几分钟,还以为是Java代码写错了。
建议在启动阶段做一次模型探测:
curl http://localhost:11434/api/tagsJava侧也写一个等价方法,启动时检查目标模型是否在已安装列表里,不在就给出明确提示“正在拉取模型,首次下载可能需要较长时间”,然后调用/api/pull触发下载,或者干脆抛错让运维处理。不要把“模型不存在”的原始堆栈直接暴露给用户,内部工具的用户通常不是程序员,一句“模型服务异常,请联系管理员”比一堆Python堆栈友好得多。
5. 扩展思路:从“问表名”到“问字段、问结构、生成SQL”的升级路线
5.1 增加list_columns意图:用同一个套路解决更细的问题
思路一旦跑通,“只查表名”很快就满足不了需求了。紧接着的自然扩展是“查某张表的字段”。实现方式完全一样,只是在Prompt的意图枚举里加一项,再加一条查询SQL。
Prompt扩展示例:
可用意图枚举: - list_tables:用户想查看数据表名称 - list_columns:用户想查看某张表的字段信息,需要额外的table参数 输出格式:{"action": "list_columns", "schema": "public", "table": "orders"} 示例: 用户:订单表有哪些字段? 输出:{"action": "list_columns", "schema": "public", "table": "orders"}对应的SQL:
SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ? ORDER BY ordinal_position;Java侧只需在TableLister里加一个queryColumns方法,并把Intent record加一个table字段。整个架构不用动,加意图、加SQL、加方法,三步完成。这就是把模型输出收敛成结构化意图的最大好处:新增功能像填表一样简单。
5.2 整理成Spring Boot服务:结合API Key和代理
命令行工具验证完了,下一步通常就是把它包成一个Spring Boot服务,供内部系统通过HTTP调用。OllamaClient和TableLister都可以直接做成@Service组件。如果服务要暴露给局域网内多个系统,建议在前置网关做一层反代,并校验API Key,防止未经认证的请求打到Ollama上。Ollama新版本也支持配置OLLAMA_API_KEY环境变量,开启后调用方需要在请求头里带Authorization: Bearer才能访问,这个功能值得打开,省得自己写一套权限系统。数据库连接直接用上一节配置好的HikariCP数据源,连接数可控,查询稳定。
5.3 个人实测体会与建议
最后说点个人体会。这个工具做下来,我最大的感触是:不要把大模型放进数据库的执行路径里,而是让它站在入口处做“翻译”。翻译错了,最多是用户得到一条“没有匹配的操作”的提示;执行错了,可能就是把整张表清空。意图白名单这个设计,从一开始就决定了这个工具是安全的。另外,如果你是第一次做类似项目,我建议先跳过Ollama,把“Java查PostgreSQL表名”这条SQL链路跑通,再加模型层。两层解耦之后,无论哪一边出问题,排查范围都能缩小一半。本地模型的推理速度虽然不是毫秒级,但查一个表名清单,几秒钟内返回完全能接受。这种“本地大模型 + 白名单意图 + 标准元数据查询”的组合,后续接查询表注释、表索引、甚至生成简单的数据字典文档,都是顺理成章的事。