SpringAI SQL 智能助手实战:用自然语言查询数据库

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. 核心实现思路

整个流程分为三步:

  1. 获取数据库 Schema:读取表结构、字段、类型、注释等信息
  2. 构造 Prompt:将 Schema 与用户自然语言问题组装成结构化提示词
  3. 调用 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 语句生成
  • 集成前端对话界面
相关推荐
摇滚侠1 小时前
git ignore 忽略 .idea 目录 全新项目(尚未提交过 .idea).idea 已经被 Git 跟踪(已提交过)
java·git·intellij-idea
嘟嘟MD1 小时前
程序员副业 | 2026年5月复盘
ai编程·创业
熟悉的新风景1 小时前
maven常用依赖
java·maven
圣殿骑士-Khtangc1 小时前
2026 AI 编程工具终极实战指南:Cursor vs Claude Code vs Copilot,开发者该怎么选?
人工智能·copilot
澹锦汐1 小时前
独立开发者的出海架构:从单一市场到全球化部署
人工智能
light blue bird1 小时前
3C 数码电子BOM 协同工作台组件
java·开发语言·jvm·windows·.net·桌面端
深度学习lover1 小时前
<数据集>yolo航拍视角垃圾识别<目标检测>
人工智能·深度学习·yolo·目标检测·数据集·航拍视角垃圾识别
孟俊宇-MJY1 小时前
CSDN AI数字营销GEO工具测评
人工智能
星马梦缘1 小时前
MCP 模型上下文协议、Agent Skills 智能体技能、Harness操作系统 课程内容
人工智能·大模型·llm·agent·智能体·mcp·skills