让业务人员直接"问"数据库:Spring AI Alibaba NL2SQL 实战指南
# 让业务人员直接"问"数据库:Spring AI Alibaba NL2SQL 实战指南
不用学 SQL,不用找开发排期,用大白话就能查数据——这不是未来,而是现在就能落地的方案。
# 一个真实的痛点
你是公司的运营负责人,想知道"上个月华东地区复购率最高的三个品类"。
在传统模式下,这个需求的链路是这样的:
你提需求 → 开发排期(3天后)→ 写 SQL → 测试 → 上线 → 你拿到数据
如果第二天你又想知道"按周维度的趋势变化"?再来一轮。
NL2SQL(Natural Language to SQL) 要解决的正是这个问题——让不懂 SQL 的人,也能用自然语言直接查询数据库。
但 NL2SQL 不是新概念。早在 ChatGPT 出现之前,学术界就在研究这个问题。为什么以前的方案没有普及?核心障碍有三个:
- Schema 理解不准:模型不知道你的业务表结构,容易"张冠李戴"
- 复杂查询搞不定:多表关联、窗口函数一上,生成质量断崖式下降
- 执行不可控:生成的 SQL 可能有语法错误,甚至误删数据
Spring AI Alibaba 的 NL2SQL 模块,正是针对这三个痛点,将阿里云析言 GBI 产品中的核心能力模块化、开源化。
# 一、整体架构:三步走
Spring AI Alibaba NL2SQL 的核心链路可以概括为三步:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 用户自然语言 │───►│ Schema 召回 │───►│ SQL 生成 │
│ "上月华东区 │ │ 找到相关表 │ │ 大模型翻译 │
│ 复购率Top3" │ │ 和字段 │ │ 为 SQL │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ SQL 执行 │
│ 返回结果 │
│ + 错误处理 │
└──────────────┘
2
3
4
5
6
7
8
9
10
11
12
# 每一步解决什么问题?
| 步骤 | 核心问题 | 解决方案 |
|---|---|---|
| Schema 召回 | 数据库可能有上百张表,模型怎么知道该用哪张? | 将表结构向量化,根据用户问题语义匹配最相关的表和字段 |
| SQL 生成 | 自然语言怎么变成准确的 SQL? | 大模型 + 数据库方言适配 + 复杂查询优化 |
| SQL 执行 | 生成的 SQL 能直接跑吗?安全吗? | 连接池管理、只读保护、错误反馈 |
# 二、环境准备
# 2.1 前置条件
- JDK 17+:Spring Boot 3.x 的最低要求
- MySQL 8.0(或其他 JDBC 兼容的数据库)
- DashScope API Key:用于调用通义千问等大模型
设置 API Key:
export AI_DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
# 2.2 准备测试数据
为了演示效果,我们先准备一个简单的电商数据库:
CREATE DATABASE ecommerce_demo;
USE ecommerce_demo;
-- 商品表
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(100),
category VARCHAR(50),
price DECIMAL(10, 2)
);
-- 订单表
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
product_id INT,
quantity INT,
order_date DATE,
region VARCHAR(50),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
-- 插入测试数据
INSERT INTO products VALUES
(1, 'iPhone 15', '手机', 5999),
(2, 'MacBook Pro', '电脑', 12999),
(3, 'AirPods Pro', '耳机', 1899);
INSERT INTO orders VALUES
(1, 101, 1, 2, '2024-01-15', '华东'),
(2, 102, 2, 1, '2024-02-20', '华北'),
(3, 101, 3, 3, '2024-03-10', '华东'),
(4, 103, 1, 1, '2024-04-05', '华南');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 三、项目搭建
# 3.1 引入依赖
创建 Spring Boot 项目后,在 pom.xml 中添加 NL2SQL 相关依赖:
<dependencies>
<!-- Spring AI Alibaba 基础依赖 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0.2</version>
</dependency>
<!-- NL2SQL 模块 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-nl2sql</artifactId>
<version>1.0.0.2</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- Web 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
同时需要配置 Maven 仓库(Spring Milestone 仓库):
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2
3
4
5
6
7
8
9
10
# 3.2 配置文件
在 application.yml 中配置数据库和大模型连接:
spring:
datasource:
url: jdbc:mysql://localhost:3306/ecommerce_demo?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
chat:
model: qwen-plus
nl2sql:
# 数据库方言
database-type: mysql
# 是否自动执行生成的 SQL(生产环境建议设为 false)
auto-execute: true
# Schema 召回策略
schema-recall:
# 召回的表数量上限
max-tables: 10
# 相似度阈值
similarity-threshold: 0.6
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 四、核心实现
# 4.1 Schema 注册
NL2SQL 的第一步是让模型"认识"你的数据库。Spring AI Alibaba NL2SQL 提供了 Schema 管理模块,可以自动扫描数据库结构并建立向量索引:
import com.alibaba.cloud.ai.nl2sql.schema.SchemaManager;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class SchemaInitializer implements ApplicationRunner {
private final SchemaManager schemaManager;
public SchemaInitializer(SchemaManager schemaManager) {
this.schemaManager = schemaManager;
}
@Override
public void run(ApplicationArguments args) throws Exception {
// 自动扫描数据库 Schema 并建立向量索引
schemaManager.initialize();
System.out.println("Schema 初始化完成,已注册表和字段信息");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这个过程会做以下几件事:
- 通过 JDBC 读取数据库的元数据(表名、字段名、字段类型、注释)
- 将元数据信息转为向量表示
- 存入向量数据库,供后续语义检索使用
关于字段注释的重要性: 如果你的表有完善的注释(COMMENT),Schema 召回的准确率会大幅提升。建议在数据库设计时就写好注释,例如:
ALTER TABLE orders MODIFY COLUMN region VARCHAR(50) COMMENT '订单所属地区';1
# 4.2 NL2SQL 服务
接下来实现核心的自然语言转 SQL 功能:
import com.alibaba.cloud.ai.nl2sql.service.Nl2SqlService;
import com.alibaba.cloud.ai.nl2sql.model.Nl2SqlResult;
import org.springframework.stereotype.Service;
@Service
public class DataQueryService {
private final Nl2SqlService nl2SqlService;
public DataQueryService(Nl2SqlService nl2SqlService) {
this.nl2SqlService = nl2SqlService;
}
/**
* 自然语言查询数据库
* @param naturalQuery 用户的自然语言问题
* @return 查询结果
*/
public Nl2SqlResult query(String naturalQuery) {
return nl2SqlService.nl2sql(naturalQuery);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Nl2SqlResult 通常包含以下信息:
- 生成的 SQL 语句
- 执行结果(如果开启了自动执行)
- 召回的 Schema 信息(用于调试和审计)
- 错误信息(如果生成或执行失败)
# 4.3 REST 接口
对外暴露一个简洁的 HTTP 接口:
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/data")
public class DataQueryController {
private final DataQueryService dataQueryService;
public DataQueryController(DataQueryService dataQueryService) {
this.dataQueryService = dataQueryService;
}
/**
* 自然语言查询接口
* GET /api/data/query?question=列出所有商品的价格
*/
@GetMapping("/query")
public DataQueryResponse query(@RequestParam String question) {
try {
var result = dataQueryService.query(question);
return DataQueryResponse.success(
result.getSql(),
result.getExecutionResult()
);
} catch (Exception e) {
return DataQueryResponse.error(e.getMessage());
}
}
// 响应 DTO
public record DataQueryResponse(
boolean success,
String sql,
Object data,
String errorMessage
) {
public static DataQueryResponse success(String sql, Object data) {
return new DataQueryResponse(true, sql, data, null);
}
public static DataQueryResponse error(String message) {
return new DataQueryResponse(false, null, null, message);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 五、测试验证
启动应用后,用几个典型场景来测试效果:
# 场景一:简单查询
curl "http://localhost:8080/api/data/query?question=列出所有商品"
预期生成的 SQL:
SELECT product_id, product_name, category, price FROM products;
# 场景二:条件过滤 + 排序
curl "http://localhost:8080/api/data/query?question=华东地区有多少个订单"
预期生成的 SQL:
SELECT COUNT(*) FROM orders WHERE region = '华东';
# 场景三:多表关联
curl "http://localhost:8080/api/data/query?question=每个品类的商品平均价格是多少"
预期生成的 SQL:
SELECT category, AVG(price) as avg_price
FROM products
GROUP BY category;
2
3
# 场景四:复杂聚合
curl "http://localhost:8080/api/data/query?question=复购次数最多的用户是谁"
预期生成的 SQL:
SELECT user_id, COUNT(*) as order_count
FROM orders
GROUP BY user_id
ORDER BY order_count DESC
LIMIT 1;
2
3
4
5
# 六、生产环境关键考量
上面的代码跑通了基本流程。但在真实业务中,还需要解决以下问题:
# 6.1 安全防护
只读保护: 绝对不要让 NL2SQL 直接拥有写权限。建议:
spring:
datasource:
# 使用只读账号
url: jdbc:mysql://localhost:3306/ecommerce_demo?useSSL=false
username: readonly_user
password: readonly_password
2
3
4
5
6
同时在代码层面增加 SQL 校验:
public boolean isSafeSql(String sql) {
String upper = sql.trim().toUpperCase();
// 只允许 SELECT 语句
return upper.startsWith("SELECT");
}
2
3
4
5
# 6.2 SQL 审核与人工确认
对于关键业务场景,建议增加"生成 → 审核 → 执行"的流程:
public Nl2SqlResult queryWithReview(String naturalQuery) {
// 第一步:生成 SQL(不执行)
Nl2SqlResult result = nl2SqlService.generateOnly(naturalQuery);
// 第二步:返回 SQL 给人工审核
// ... 审核流程 ...
// 第三步:审核通过后执行
if (approved) {
result = nl2SqlService.executeSql(result.getSql());
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 6.3 查询超时与资源限制
防止慢查询拖垮数据库:
import java.sql.Statement;
import java.util.concurrent.*;
public Object executeWithTimeout(String sql, int timeoutSeconds) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Object> future = executor.submit(() -> {
// 执行 SQL
return jdbcTemplate.queryForList(sql);
});
try {
return future.get(timeoutSeconds, TimeUnit.SECONDS);
} catch (TimeoutException e) {
future.cancel(true);
throw new RuntimeException("查询超时(" + timeoutSeconds + "秒)");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 6.4 结果缓存
对于高频查询,可以加一层缓存:
import org.springframework.cache.annotation.Cacheable;
@Cacheable(value = "nl2sql", key = "#naturalQuery")
public Nl2SqlResult query(String naturalQuery) {
return nl2SqlService.nl2sql(naturalQuery);
}
2
3
4
5
6
# 6.5 多数据源支持
企业通常有多个数据库(订单库、用户库、商品库)。Spring AI Alibaba NL2SQL 支持多数据源配置:
@Configuration
public class MultiDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.order")
public DataSource orderDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.user")
public DataSource userDataSource() {
return DataSourceBuilder.create().build();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 七、提升 NL2SQL 准确率的实战技巧
# 7.1 完善数据库注释
这是最有效的优化手段。模型对字段的理解完全依赖元数据中的注释信息。
-- 好的注释
ALTER TABLE orders ADD COLUMN order_status TINYINT COMMENT '订单状态:1-待支付,2-已支付,3-已发货,4-已完成,5-已取消';
-- 不好的注释(或没有注释)
ALTER TABLE orders ADD COLUMN order_status TINYINT;
2
3
4
5
# 7.2 提供业务术语映射
用户的自然语言表述和数据库字段名往往不一致。可以建立术语映射表:
public class BusinessTermMapping {
private static final Map<String, String> TERM_MAP = Map.of(
"销售额", "price * quantity",
"复购", "user_id 出现次数 > 1",
"华东区", "region IN ('上海', '江苏', '浙江', '安徽')"
);
public static String enrichQuery(String naturalQuery) {
for (var entry : TERM_MAP.entrySet()) {
naturalQuery = naturalQuery.replace(entry.getKey(), entry.getValue());
}
return naturalQuery;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 7.3 Few-Shot 示例增强
在 Prompt 中提供几个正确的"问题 → SQL"示例,可以显著提升生成质量:
示例1:
问题:找出价格超过100元的商品
SQL:SELECT * FROM products WHERE price > 100
示例2:
问题:统计每个地区的订单数量
SQL:SELECT region, COUNT(*) FROM orders GROUP BY region
现在请回答:
问题:{user_question}
SQL:
2
3
4
5
6
7
8
9
10
11
# 7.4 选择合适的模型
不同模型在 NL2SQL 任务上的表现差异很大:
| 模型 | 适用场景 | 成本 |
|---|---|---|
| qwen-plus | 日常使用,性价比高 | 低 |
| qwen-max | 复杂查询,多表关联 | 中 |
| XiYan-SQL(开源) | 专注 NL2SQL 的专用模型 | 免费 |
对于简单的单表查询,qwen-plus 已经够用;涉及多表 JOIN、子查询、窗口函数的场景,建议使用 qwen-max 或专用的 SQL 生成模型。
# 八、常见问题排查
Q:生成的 SQL 语法错误怎么办?
A:排查顺序:
- 确认数据库方言配置正确(
database-type: mysql) - 检查 Schema 是否正确注册(查看初始化日志)
- 尝试换用更强的模型(qwen-max)
- 在 Prompt 中增加 Few-Shot 示例
Q:召回的表不对,导致生成的 SQL 完全错误?
A:
- 检查字段注释是否完善
- 降低
similarity-threshold阈值 - 增加
max-tables数量 - 手动指定相关表(通过 API 参数)
Q:回答速度太慢?
A:NL2SQL 涉及多次 LLM 调用(Schema 召回 + SQL 生成),整体耗时通常在 3-8 秒。优化方向:
- 使用流式输出,先返回 SQL 再执行
- 缓存常见查询的结果
- 选择响应更快的模型
Q:依赖拉取失败?
A:确认已配置 Spring Milestones 仓库(见上文 3.1 节)。如果仍然失败,可以尝试使用快照版本或检查网络连接。
# 九、总结
NL2SQL 不是要取代 SQL,而是要降低数据查询的门槛。它的最佳应用场景是:
- 业务人员的自助分析:不用等开发排期,自己问数据
- 数据产品的底层引擎:为 BI 工具、智能客服提供数据查询能力
- 开发者的效率工具:快速生成复杂 SQL,减少手写错误
Spring AI Alibaba NL2SQL 的价值在于:
- 开箱即用:引入依赖即可启动服务,不需要从零搭建
- 模块化设计:Schema 召回、SQL 生成、SQL 执行三个环节解耦,可按需组合
- 企业级能力:多数据源、安全防护、错误处理一应俱全
- 持续迭代:背靠阿里云析言 GBI 产品,能力持续增强
对于 Java 团队来说,这意味着你可以在几天内搭建一个"对话式数据查询"系统,而不需要投入大量人力去造轮子。