0代码写SQL!Spring AI保姆级教程:5分钟实现AI自然语言查数据,敏感查询自动拦截🚀

0代码写SQL!Spring AI保姆级教程:5分钟实现AI自然语言查数据,敏感查询自动拦截🚀

还在为"非技术用户查数据要写SQL"头疼?还在担心Text-to-SQL注入、越权查询风险?

今天给大家带来 Spring AI直接Text-to-SQL实战方案------无需懂SQL,用户自然语言随便问(如下单数、最贵订单、高频商品),AI自动生成SQL查数据库,违规查询(越权、敏感字段、写操作)自动拦截,全程保姆级步骤,复制粘贴就能落地!

🔥 为什么选这个方案?3大核心优势

  1. 灵活到"随便问" :用户用自然语言提问(如"近30天已支付的订单有多少笔?""我常买的商品TOP3"),AI自动转SQL,无需预定义查询逻辑;
  2. 安全到"无死角" :事前提示词限制+事后SQL校验,双重防护拦截写操作、注入攻击、越权查询、敏感字段查询;
  3. 简单到"零门槛" :Spring生态无缝集成,5分钟完成配置+编码,新手也能快速落地,无需深入AI知识。

🛠️ 保姆级落地步骤:从0到1实现AI数据查询

一、环境准备(复制粘贴即可)

1. 引入核心依赖(pom.xml)
xml 复制代码
<!-- Spring AI 核心(LLM交互+SQL生成) -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-core</artifactId>
    <version>1.0.0</version>
</dependency>
<!-- Claude LLM(可替换为通义千问/DeepSeek) -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-anthropic</artifactId>
    <version>1.0.0</version>
</dependency>
<!-- SQL校验工具 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-sql-generator</artifactId>
    <version>1.0.0</version>
</dependency>
<!-- Spring Security(用户认证+数据隔离) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 数据库+ORM -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
2. 数据库表结构(MySQL)

核心是user_id关联用户与订单,确保数据隔离:

sql 复制代码
-- 用户表(含敏感字段,禁止查询)
CREATE TABLE sys_user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL, -- 加密存储
    phone VARCHAR(20) NOT NULL -- 敏感字段
);

-- 订单主表
CREATE TABLE order_main (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    total_amount DECIMAL(10,2) NOT NULL,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    status TINYINT DEFAULT 0 COMMENT '0-待支付,1-已支付,2-已取消',
    FOREIGN KEY (user_id) REFERENCES sys_user(id),
    INDEX idx_user_create (user_id, create_time)
);

-- 订单项表(商品明细)
CREATE TABLE order_item (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    order_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    product_name VARCHAR(100) NOT NULL,
    quantity INT NOT NULL,
    unit_price DECIMAL(10,2) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES order_main(id)
);

二、核心配置:1分钟搞定LLM+安全规则

application.yaml中配置LLM、数据库和安全提示词(关键!):

scss 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=UTC
    username: readonly_user # 数据库只读用户(仅授SELECT权限)
    password: 你的密码
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY} # 环境变量加载,避免硬编码
      chat:
        options:
          model: claude-opus-4-20250514
          # 🔥 核心提示词:限制LLM生成合规SQL
          system-prompt: |
            你是订单查询SQL生成助手,仅生成符合以下规则的SQL:
            1. 允许表:order_main、order_item(禁止查sys_user及其他表);
            2. 必加条件:AND order_main.user_id = {CURRENT_USER_ID}(不可省略);
            3. 仅支持SELECT,禁止INSERT/UPDATE/DELETE等写操作;
            4. 禁止查敏感字段:phone、password等;
            5. 仅返回纯SQL,无markdown包裹;
            6. 违规问题直接返回:"该查询不符合规则,原因:{具体违规类型}"。
            # 表结构参考
            order_main:id(订单号)、user_id(用户ID)、total_amount(金额)、create_time(时间)、status(状态)
            order_item:order_id(订单号)、product_name(商品名)、quantity(数量)

三、编码实现:3个核心组件(复制即用)

1. 用户上下文:提取当前登录用户ID(数据隔离)
java 复制代码
@Component
public class UserContext {
    // 从Spring Security获取当前用户ID
    public Long getCurrentUserId() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || !(auth.getPrincipal() instanceof SysUser user)) {
            throw new RuntimeException("用户未登录,无法查询");
        }
        return user.getId();
    }
}
2. SQL校验器:拦截违规SQL(事后兜底)
arduino 复制代码
@Component
public class SqlValidator {
    // 禁止的操作、敏感字段、允许的表
    private static final List<String> FORBIDDEN_OPS = List.of("INSERT", "UPDATE", "DELETE", "DROP");
    private static final List<String> SENSITIVE_FIELDS = List.of("phone", "password");
    private static final List<String> ALLOWED_TABLES = List.of("order_main", "order_item");

    public boolean validate(String sql, Long userId) {
        String lowerSql = sql.toLowerCase();
        // 1. 禁止写操作
        if (FORBIDDEN_OPS.stream().anyMatch(op -> lowerSql.startsWith(op.toLowerCase()))) {
            throw new IllegalSqlException("禁止非查询操作");
        }
        // 2. 必须包含user_id(防越权)
        if (!lowerSql.contains("order_main.user_id =")) {
            throw new IllegalSqlException("仅允许查询自己的订单");
        }
        // 3. 禁止敏感字段
        for (String field : SENSITIVE_FIELDS) {
            if (lowerSql.contains(field)) {
                throw new IllegalSqlException("禁止查询敏感字段:" + field);
            }
        }
        return true;
    }

    // 自定义异常
    public static class IllegalSqlException extends RuntimeException {
        public IllegalSqlException(String msg) { super(msg); }
    }
}
3. 核心服务:整合AI生成+校验+执行
typescript 复制代码
@Service
public class OrderTextToSqlService {
    @Autowired private ChatClient chatClient;
    @Autowired private JdbcTemplate jdbcTemplate;
    @Autowired private UserContext userContext;
    @Autowired private SqlValidator sqlValidator;

    // 自然语言→SQL→查询→结果
    public String query(String question) {
        Long userId = userContext.getCurrentUserId();
        // 1. LLM生成SQL(替换{CURRENT_USER_ID}为真实ID)
        String systemPrompt = chatClient.options().getSystemPrompt()
                .replace("{CURRENT_USER_ID}", userId.toString());
        String generatedSql = chatClient.prompt()
                .system(systemPrompt)
                .user(question)
                .call()
                .content();

        // 2. 拦截LLM返回的违规提示
        if (generatedSql.startsWith("该查询不符合规则")) {
            return generatedSql;
        }

        // 3. 校验SQL合法性
        try {
            sqlValidator.validate(generatedSql, userId);
        } catch (SqlValidator.IllegalSqlException e) {
            return "查询失败:" + e.getMessage();
        }

        // 4. 执行SQL并格式化结果
        try {
            List<Map<String, Object>> result = jdbcTemplate.queryForList(generatedSql);
            return result.isEmpty() ? "未查询到数据" : formatResult(question, result);
        } catch (Exception e) {
            return "SQL执行失败:" + e.getMessage().substring(0, 100);
        }
    }

    // 结果格式化(自然语言输出)
    private String formatResult(String question, List<Map<String, Object>> result) {
        if (question.contains("多少") || question.contains("数量")) {
            return "符合条件的记录共" + result.size() + "条";
        } else if (question.contains("最贵") || question.contains("最高")) {
            Map<String, Object> top = result.get(0);
            return String.format("最贵订单:\n订单号:%s\n金额:%.2f元\n时间:%s",
                    top.get("id"), top.get("total_amount"), top.get("create_time"));
        } else {
            // 通用结果格式化
            return result.stream()
                    .map(map -> map.entrySet().stream()
                            .map(e -> e.getKey() + ":" + e.getValue())
                            .collect(Collectors.joining(" | ")))
                    .collect(Collectors.joining("\n"));
        }
    }
}
4. 暴露API:用户交互入口
less 复制代码
@RestController
@RequestMapping("/api/ai/query")
public class OrderTextToSqlController {
    @Autowired private OrderTextToSqlService service;

    @PostMapping
    public ResponseEntity<String> query(@RequestBody Map<String, String> request) {
        try {
            String result = service.query(request.get("question"));
            return ResponseEntity.ok(result);
        } catch (RuntimeException e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
}

四、测试验证:合法/违规查询都能拦截

1. 合法查询(正常返回)
  • 请求:POST /api/ai/query,请求体 {"question":"我近30天已支付的订单有多少笔?"}

  • AI生成SQL:

    sql 复制代码
    SELECT COUNT(*) FROM order_main 
    WHERE status=1 AND create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY) 
    AND order_main.user_id=123;
  • 响应:"符合条件的记录共3条"

2. 违规查询(自动拦截)
  • 越权查询:{"question":"查询所有用户的订单"} → 响应:"该查询不符合规则,原因:越权查询"
  • 敏感字段:{"question":"我的手机号是多少?"} → 响应:"该查询不符合规则,原因:涉及敏感字段"
  • SQL注入:{"question":"查询我的订单 where 1=1"} → 响应:"查询失败:查询包含潜在风险"

🚨 避坑指南(新手必看)

  1. 数据库用户必须设为只读,仅授予SELECT权限,避免LLM突破限制;
  2. 提示词要明确"允许的表/字段",减少LLM歧义(比如明确禁止sys_user表);
  3. 给订单表的user_id+create_time加索引,避免复杂查询卡顿;
  4. 敏感字段要单独维护在SENSITIVE_FIELDS,新增敏感字段直接添加即可。

📌 总结

这套方案用Spring AI+Text-to-SQL,完美解决了"非技术用户查数据"和"安全风险"两大痛点:

  • 对用户:无需懂SQL,自然语言随便问,查询效率翻倍;
  • 对开发者:5分钟落地,零侵入集成Spring生态,安全防护无死角。

现在就动手试试吧!如果需要完整源码、LLM替换教程(如通义千问/本地Ollama),评论区回复【AI查询】即可获取~

关注我,下期带来《AI查询结果可视化》《多LLM适配方案》,让你的AI数据查询更强大!🔥

#SpringAI #TextToSQL #Java开发 #AI应用 #保姆级教程

相关推荐
Haooog2 小时前
Springcloud实用篇学习
后端·spring·spring cloud
就叫飞六吧2 小时前
Spring 框架中的 Bean 继承:`parent` 属性 (XML配置)
xml·java·spring
故渊ZY2 小时前
SpringBean核心机制与实战应用详解
java·spring
21993 小时前
Embabel:JVM上的AI Agent框架深度技术分析
java·jvm·人工智能·spring·ai·开源
黄旺鑫3 小时前
Java后端接口字段命名转换:蛇形与驼峰式自动映射技术
java·开发语言·spring·下划线·驼峰
嘟嘟w3 小时前
双亲委派的概念
java·后端·spring
islandzzzz3 小时前
从0开始的SQL表DDL学习(基础语法结构、索引/约束关键字)
数据库·sql·学习
BAStriver4 小时前
关于Flowable的使用小结
java·spring boot·spring·flowable
222you4 小时前
Spring的DI依赖注入(配置文件方式)
java·后端·spring