1. 引言
在传统开发中,查询数据库通常需要编写 SQL 语句,这对非技术用户来说门槛较高。借助 SpringAI 框架,我们可以构建一个智能 SQL 助手,让用户通过自然语言直接查询数据库,大幅降低数据访问门槛。
本文将带你从零搭建一个基于 SpringAI 的 SQL 智能助手,实现「说人话,查数据」的效果。
2. 技术栈与前置准备
2.1 技术选型
| 组件 | 版本 | 说明 |
|---|---|---|
| Spring Boot | 3.2+ | 应用框架 |
| SpringAI | 1.0.0-M6+ | AI 集成框架 |
| JDK | 17+ | 运行环境 |
| MySQL / H2 | 任意 | 目标数据库 |
| OpenAI / 兼容 API | --- | LLM 服务 |
2.2 核心依赖
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
3. 核心实现思路
整个流程分为三步:
- 获取数据库 Schema:读取表结构、字段、类型、注释等信息
- 构造 Prompt:将 Schema 与用户自然语言问题组装成结构化提示词
- 调用 LLM 生成 SQL:由 AI 模型返回对应的 SQL 查询语句
#mermaid-svg-7RnC0OHXcHYLE17g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7RnC0OHXcHYLE17g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7RnC0OHXcHYLE17g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7RnC0OHXcHYLE17g .error-icon{fill:#552222;}#mermaid-svg-7RnC0OHXcHYLE17g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7RnC0OHXcHYLE17g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7RnC0OHXcHYLE17g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7RnC0OHXcHYLE17g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7RnC0OHXcHYLE17g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7RnC0OHXcHYLE17g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7RnC0OHXcHYLE17g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7RnC0OHXcHYLE17g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7RnC0OHXcHYLE17g .marker.cross{stroke:#333333;}#mermaid-svg-7RnC0OHXcHYLE17g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7RnC0OHXcHYLE17g p{margin:0;}#mermaid-svg-7RnC0OHXcHYLE17g .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7RnC0OHXcHYLE17g .cluster-label text{fill:#333;}#mermaid-svg-7RnC0OHXcHYLE17g .cluster-label span{color:#333;}#mermaid-svg-7RnC0OHXcHYLE17g .cluster-label span p{background-color:transparent;}#mermaid-svg-7RnC0OHXcHYLE17g .label text,#mermaid-svg-7RnC0OHXcHYLE17g span{fill:#333;color:#333;}#mermaid-svg-7RnC0OHXcHYLE17g .node rect,#mermaid-svg-7RnC0OHXcHYLE17g .node circle,#mermaid-svg-7RnC0OHXcHYLE17g .node ellipse,#mermaid-svg-7RnC0OHXcHYLE17g .node polygon,#mermaid-svg-7RnC0OHXcHYLE17g .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7RnC0OHXcHYLE17g .rough-node .label text,#mermaid-svg-7RnC0OHXcHYLE17g .node .label text,#mermaid-svg-7RnC0OHXcHYLE17g .image-shape .label,#mermaid-svg-7RnC0OHXcHYLE17g .icon-shape .label{text-anchor:middle;}#mermaid-svg-7RnC0OHXcHYLE17g .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7RnC0OHXcHYLE17g .rough-node .label,#mermaid-svg-7RnC0OHXcHYLE17g .node .label,#mermaid-svg-7RnC0OHXcHYLE17g .image-shape .label,#mermaid-svg-7RnC0OHXcHYLE17g .icon-shape .label{text-align:center;}#mermaid-svg-7RnC0OHXcHYLE17g .node.clickable{cursor:pointer;}#mermaid-svg-7RnC0OHXcHYLE17g .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7RnC0OHXcHYLE17g .arrowheadPath{fill:#333333;}#mermaid-svg-7RnC0OHXcHYLE17g .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7RnC0OHXcHYLE17g .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7RnC0OHXcHYLE17g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7RnC0OHXcHYLE17g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7RnC0OHXcHYLE17g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7RnC0OHXcHYLE17g .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7RnC0OHXcHYLE17g .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7RnC0OHXcHYLE17g .cluster text{fill:#333;}#mermaid-svg-7RnC0OHXcHYLE17g .cluster span{color:#333;}#mermaid-svg-7RnC0OHXcHYLE17g div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7RnC0OHXcHYLE17g .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7RnC0OHXcHYLE17g rect.text{fill:none;stroke-width:0;}#mermaid-svg-7RnC0OHXcHYLE17g .icon-shape,#mermaid-svg-7RnC0OHXcHYLE17g .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7RnC0OHXcHYLE17g .icon-shape p,#mermaid-svg-7RnC0OHXcHYLE17g .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7RnC0OHXcHYLE17g .icon-shape .label rect,#mermaid-svg-7RnC0OHXcHYLE17g .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7RnC0OHXcHYLE17g .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7RnC0OHXcHYLE17g .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7RnC0OHXcHYLE17g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户输入自然语言
获取数据库 Schema
组装 Prompt
调用 LLM
生成 SQL
执行查询并返回结果
4. 代码实现
4.1 数据库 Schema 读取
java
@Component
public class SchemaReader {
@Resource
private JdbcTemplate jdbcTemplate;
public String getTableSchema(String tableName) {
String sql = """
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ?
""";
List<Map<String, Object>> columns = jdbcTemplate.queryForList(sql, tableName);
StringBuilder sb = new StringBuilder();
sb.append("表名:").append(tableName).append("\n");
for (Map<String, Object> col : columns) {
sb.append("- ").append(col.get("COLUMN_NAME"))
.append(" (").append(col.get("DATA_TYPE")).append(")")
.append(":").append(col.get("COLUMN_COMMENT")).append("\n");
}
return sb.toString();
}
}
4.2 Prompt 模板设计
java
public class SqlPromptTemplate {
public static String build(String schema, String userQuestion) {
return """
你是一个 SQL 专家。根据以下数据库表结构,将用户的自然语言问题转换为 SQL 查询语句。
表结构:
%s
用户问题:%s
请只返回 SQL 语句,不要额外解释。
""".formatted(schema, userQuestion);
}
}
4.3 核心服务类
java
@Service
public class SqlAssistantService {
@Resource
private ChatClient chatClient;
@Resource
private SchemaReader schemaReader;
public String ask(String question, String tableName) {
String schema = schemaReader.getTableSchema(tableName);
String prompt = SqlPromptTemplate.build(schema, question);
return chatClient.prompt(prompt).call().content();
}
}
4.4 Controller 接口
java
@RestController
@RequestMapping("/api/sql-assistant")
public class SqlAssistantController {
@Resource
private SqlAssistantService sqlAssistantService;
@PostMapping("/ask")
public String ask(@RequestParam String question,
@RequestParam String tableName) {
return sqlAssistantService.ask(question, tableName);
}
}
5. 配置与运行
5.1 application.yml
yaml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com
chat:
options:
model: gpt-4o-mini
datasource:
url: jdbc:mysql://localhost:3306/your_db
username: root
password: your_password
5.2 启动与测试
启动 Spring Boot 应用后,使用 curl 测试:
bash
curl -X POST "http://localhost:8080/api/sql-assistant/ask" \
-d "question=查询最近7天的订单数量" \
-d "tableName=orders"
返回示例:
sql
SELECT COUNT(*) AS order_count
FROM orders
WHERE order_date >= CURDATE() - INTERVAL 7 DAY;
6. 进阶优化
6.1 多表关联
对于涉及多表的查询,可以一次性传入多个表的 Schema:
java
public String getMultiTableSchema(List<String> tableNames) {
return tableNames.stream()
.map(this::getTableSchema)
.collect(Collectors.joining("\n---\n"));
}
6.2 安全校验
为防止 SQL 注入或危险操作,可以在执行前做校验:
java
public boolean isReadOnlyQuery(String sql) {
String trimmed = sql.trim().toUpperCase();
return trimmed.startsWith("SELECT") || trimmed.startsWith("WITH");
}
6.3 结果缓存
对相同问题可做缓存,减少 LLM 调用次数:
java
@Cacheable(value = "sqlCache", key = "#question + ':' + #tableName")
public String ask(String question, String tableName) {
// ...
}
7. 总结
本文通过 SpringAI 框架实现了一个 SQL 智能助手,核心思路是:读取 Schema → 构造 Prompt → 调用 LLM → 生成 SQL。你可以在此基础上扩展多表查询、安全校验、缓存优化等能力,打造一个生产级的自然语言查询工具。
下一步可以尝试:
- 接入更多数据库类型(PostgreSQL、Oracle 等)
- 支持 DDL 语句生成
- 集成前端对话界面