0代码写SQL!Spring AI保姆级教程:5分钟实现AI自然语言查数据,敏感查询自动拦截🚀
还在为"非技术用户查数据要写SQL"头疼?还在担心Text-to-SQL注入、越权查询风险?
今天给大家带来 Spring AI直接Text-to-SQL实战方案------无需懂SQL,用户自然语言随便问(如下单数、最贵订单、高频商品),AI自动生成SQL查数据库,违规查询(越权、敏感字段、写操作)自动拦截,全程保姆级步骤,复制粘贴就能落地!
🔥 为什么选这个方案?3大核心优势
- 灵活到"随便问" :用户用自然语言提问(如"近30天已支付的订单有多少笔?""我常买的商品TOP3"),AI自动转SQL,无需预定义查询逻辑;
- 安全到"无死角" :事前提示词限制+事后SQL校验,双重防护拦截写操作、注入攻击、越权查询、敏感字段查询;
- 简单到"零门槛" :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:
sqlSELECT 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"}→ 响应:"查询失败:查询包含潜在风险"
🚨 避坑指南(新手必看)
- 数据库用户必须设为只读,仅授予SELECT权限,避免LLM突破限制;
- 提示词要明确"允许的表/字段",减少LLM歧义(比如明确禁止sys_user表);
- 给订单表的
user_id+create_time加索引,避免复杂查询卡顿; - 敏感字段要单独维护在
SENSITIVE_FIELDS,新增敏感字段直接添加即可。
📌 总结
这套方案用Spring AI+Text-to-SQL,完美解决了"非技术用户查数据"和"安全风险"两大痛点:
- 对用户:无需懂SQL,自然语言随便问,查询效率翻倍;
- 对开发者:5分钟落地,零侵入集成Spring生态,安全防护无死角。
现在就动手试试吧!如果需要完整源码、LLM替换教程(如通义千问/本地Ollama),评论区回复【AI查询】即可获取~
关注我,下期带来《AI查询结果可视化》《多LLM适配方案》,让你的AI数据查询更强大!🔥
#SpringAI #TextToSQL #Java开发 #AI应用 #保姆级教程