1. 引言
在上一篇文章《SpringAI 入门实战:从 ChatModel 到 FunctionCall 的完整体验》中,我们体验了 SpringAI 如何让大模型与 Java 应用无缝集成。今天,我们将更进一步,探索 SpringAI 的一个非常实用的落地场景------SQL 智能助手。
想象一下,你不再需要记住复杂的 SQL 语法,只需要用自然语言提问:"统计出版书籍前三名的出版社",系统就能自动生成并执行 SQL 语句,返回查询结果。这就是我们今天要实现的"SQL 智能助手"。
2. 项目目标与基础思路
2.1 项目目标
我们将使用 SpringAI 搭建一个 RESTful API,实现以下功能:
- 用户通过 HTTP 接口输入自然语言问题(如"查询所有作者")。
- 系统将问题与数据库表结构(DDL)一起构建成 Prompt,发送给大模型。
- 大模型返回对应的 SQL 查询语句。
- 系统自动执行该 SQL 语句,并将结果返回给用户。
2.2 基础思路
整个流程的核心逻辑非常清晰,可以分为以下几步:
- 接收用户提问 :将用户的自然语言问题作为
UserMessage。 - 构建 Prompt:将用户的问题和本地数据库的表结构(DDL)一起,填充到一个精心设计的提示词模板中。
- 调用大模型:将构建好的 Prompt 发送给大模型,让大模型理解数据库结构并生成对应的 SQL 语句。
- 执行 SQL:在本地数据库中执行大模型返回的 SQL 语句,获取查询结果。
- 返回结果:将 SQL 语句和查询结果一并返回给用户。
3. 环境准备与数据初始化
3.1 准备测试数据
首先,我们需要准备一个简单的图书管理系统数据库。包含三张表:Authors(作者)、Publishers(出版社)和 Books(书籍)。
Schema.sql(数据库表结构):
sql
create table Authors (
id int not null auto_increment,
firstName varchar(255) not null,
lastName varchar(255) not null,
primary key (id)
);
create table Publishers (
id int not null auto_increment,
name varchar(255) not null,
primary key (id)
);
create table Books (
id int not null auto_increment,
isbn varchar(255) not null,
title varchar(255) not null,
author_ref int not null,
publisher_ref int not null,
primary key (id),
foreign key (author_ref) references Authors(id),
foreign key (publisher_ref) references Publishers(id)
);
Data.sql(测试数据):
sql
insert into Authors (firstName, lastName) values ('Craig', 'Walls');
insert into Authors (firstName, lastName) values ('Josh', 'Long');
insert into Authors (firstName, lastName) values ('Ken', 'Kousen');
insert into Authors (firstName, lastName) values ('Venkat', 'Subramaniam');
insert into Publishers (name) values ('Manning Publications');
insert into Publishers (name) values ('OReilly Media');
insert into Publishers (name) values ('Pragmatic Bookshelf');
insert into Books (isbn, title, author_ref, publisher_ref)
values ('9781617292545', 'Spring Boot in Action', 1, 1);
insert into Books (isbn, title, author_ref, publisher_ref)
values ('9781617297571', 'Spring in Action', 1, 1);
insert into Books (isbn, title, author_ref, publisher_ref)
values ('9781680507256', 'Build Talking Apps for Alexa', 1, 3);
insert into Books (isbn, title, author_ref, publisher_ref)
values ('9781934356401', 'Modular Java', 1, 3);
insert into Books (isbn, title, author_ref, publisher_ref)
values ('9781449374648', 'Cloud Native Java', 2, 2);
insert into Books (isbn, title, author_ref, publisher_ref)
values ('9781680508222', 'Help Your Boss Help You', 3, 3);
insert into Books (isbn, title, author_ref, publisher_ref)
values ('9781492046677', 'Kotlin Cookbook', 3, 2);
insert into Books (isbn, title, author_ref, publisher_ref)
values ('9781680509816', 'Cruising Along with Java', 4, 3);
insert into Books (isbn, title, author_ref, publisher_ref)
values ('9781680506358', 'Programming Kotlin', 4, 3);
3.2 引入依赖与配置
在 pom.xml 中添加 MySQL 驱动和 MyBatis-Plus(用于简化数据库操作,当然你也可以直接用 JdbcTemplate)的依赖。
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
在 application.properties 中配置数据库连接信息:
properties
spring.datasource.url=jdbc:mysql://localhost:3306/gpttest?characterEncoding=utf8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
4. 核心代码实现
4.1 创建 Prompt 模板
这是整个功能的关键。我们需要一个提示词模板,告诉大模型它的角色和任务。创建一个名为 sql-prompt-template.st 的文件,放在 resources 目录下。
text
Given the DDL in the DDL section, write an SQL query to answer the question in the QUESTION section.
Only produce select queries. If the question would result in an insert, update, or delete, or if the query would alter the DDL in any way, say that the operation isn't supported. If the question can't be answered, say that the DDL doesn't support answering that question.
Answer with the raw SQL query only; no markdown or other punctuation that isn't part of the query itself.
DDL
{ddl}
QUESTION
{question}
这个模板清晰地定义了大模型的行为:
- 角色:一个 SQL 生成器。
- 约束 :只生成
SELECT查询,拒绝INSERT/UPDATE/DELETE操作。 - 输出格式:只输出纯 SQL,不包含任何 Markdown 标记。
4.2 编写 Controller
创建一个 SqlExecController,注入 ChatClient 和 JdbcTemplate。
java
package com.roy.sqlgenerate;
import org.springframework.core.io.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.nio.charset.Charset;
@RestController
public class SqlExecController {
@Value("classpath:/schema.sql")
private Resource ddlResource;
@Value("classpath:/sql-prompt-template.st")
private Resource sqlPromptTemplateResource;
private final ChatClient aiClient;
private final JdbcTemplate jdbcTemplate;
public SqlExecController(ChatClient.Builder aiClientBuilder, JdbcTemplate jdbcTemplate) {
this.aiClient = aiClientBuilder.build();
this.jdbcTemplate = jdbcTemplate;
}
@GetMapping(path = "/sql")
public Answer sql(@RequestParam("question") String question) throws IOException {
// 1. 读取 DDL
String schema = ddlResource.getContentAsString(Charset.defaultCharset());
// 2. 调用大模型生成 SQL
String query = aiClient.prompt()
.user(userSpec -> userSpec
.text(sqlPromptTemplateResource)
.param("question", question)
.param("ddl", schema)
)
.call()
.content();
// 3. 清理大模型返回的 SQL(去除可能的 Markdown 标记)
query = query.replace("```sql", "").replace("```", "").trim();
System.out.println("Generated SQL: " + query);
// 4. 执行 SQL 并返回结果
if (query.toLowerCase().startsWith("select")) {
return new Answer(query, jdbcTemplate.queryForList(query));
}
throw new RuntimeException("Unsupported operation: " + query);
}
// 用于封装返回结果的 Record
public record Answer(String sql, List<Map<String, Object>> result) {}
}
4.3 测试运行
启动项目,使用 curl 或 Postman 进行测试。
请求:
bash
curl "http://localhost:8080/sql?question=统计出版书籍前三名的出版社"
响应(示例):
json
{
"sql": "SELECT p.name, COUNT(b.id) AS book_count FROM Publishers p JOIN Books b ON p.id = b.publisher_ref GROUP BY p.name ORDER BY book_count DESC LIMIT 3",
"result": [
{
"name": "Manning Publications",
"book_count": 2
},
{
"name": "Pragmatic Bookshelf",
"book_count": 4
},
{
"name": "OReilly Media",
"book_count": 2
}
]
}
5. 应用扩展与思考
一个简单的 SQL 智能助手已经诞生了。但如何让大模型更稳定、更准确地生成结果,是 AI 应用落地中需要重点考虑的问题。以下是一些扩展思路:
- 提供更多上下文:除了 DDL,还可以提供一些示例数据(DML)或常见的查询模式,帮助大模型更好地理解数据。
- 结果后处理:对于复杂的聚合查询,可以对大模型返回的 SQL 进行语法校验,或者对查询结果进行格式化、图表渲染等。
- 多轮对话 :结合
ChatMemory,让用户可以对上一次的查询结果进行追问,例如"再查一下这些出版社的地址"。 - 安全审计:在执行 SQL 前,增加一个审核步骤,确保生成的 SQL 不会对数据库造成破坏(虽然我们的 Prompt 已经做了限制,但双重保险更安全)。
6. 总结
通过本文,我们体验了 SpringAI 在"Text-to-SQL"这一经典场景下的强大能力。我们只用了不到 50 行核心代码,就搭建了一个功能完整的 SQL 智能助手。这充分展示了 SpringAI 框架的简洁与高效,让开发者能够将更多精力放在业务逻辑和 AI 应用的创新上。
从 ChatModel 到 FunctionCall,再到今天的 SQL 智能助手,SpringAI 正在不断降低 AI 应用开发的门槛。希望这篇文章能给你带来启发,快去动手试试吧!