AI 自然语言转SQL

1、核心设计思路

Text-to-SQL 的本质是让大模型根据数据库表结构(元数据)用户自然语言问题 生成 SQL。当数据库表较多时,把所有表结构都塞进 Prompt 会超出上下文限制,所以需要用 RAG 做"表选择"------先根据用户问题从向量库中检索相关的表结构,再交给 LLM 生成 SQL。

2、使用框架和工具

  • 技术栈:Ollama + Qwen3.5:4b (作为聊天模型)、nomic-embed-text:latest (作为嵌入模型)、Qdrant(作为向量数据库),并集成了 RAG 架构来管理数据库表元数据。
  • 大模型:ollama/qwen3.5:4b 负责自然语言转 SQL、问答
  • 向量嵌入:ollama/nomic-embed-text:latest 文本向量化
  • 向量库:Qdrant 存储表结构元数据向量,做检索增强
  • 框架:Spring AI 2.0(统一封装 Ollama、Qdrant、Embedding)

3、数据库表

电商订单系统为例,包含用户、商品、订单、订单明细等表:

复制代码
-- ----------------------------
-- Table structure for carts
-- ----------------------------
DROP TABLE IF EXISTS `carts`;
CREATE TABLE `carts`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NULL DEFAULT NULL COMMENT '用户ID',
  `product_id` int NULL DEFAULT NULL COMMENT '商品ID',
  `quantity` int NOT NULL COMMENT '数量',
  `selected` tinyint(1) NULL DEFAULT 1 COMMENT '是否选中',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `uk_user_product`(`user_id` ASC, `product_id` ASC) USING BTREE,
  INDEX `product_id`(`product_id` ASC) USING BTREE,
  CONSTRAINT `carts_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `carts_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `carts_chk_1` CHECK (`quantity` > 0)
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '购物车表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of carts
-- ----------------------------
INSERT INTO `carts` VALUES (1, 1, 3, 1, 1, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `carts` VALUES (2, 1, 5, 2, 1, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `carts` VALUES (3, 2, 4, 1, 0, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `carts` VALUES (4, 3, 1, 1, 1, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `carts` VALUES (5, 4, 2, 2, 1, '2026-07-18 22:26:42', '2026-07-18 22:26:42');

-- ----------------------------
-- Table structure for categories
-- ----------------------------
DROP TABLE IF EXISTS `categories`;
CREATE TABLE `categories`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分类名称',
  `parent_id` int NULL DEFAULT NULL COMMENT '父分类ID',
  `level` int NULL DEFAULT 1 COMMENT '分类层级',
  `sort_order` int NULL DEFAULT 0 COMMENT '排序序号',
  `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '启用' COMMENT '状态',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `parent_id`(`parent_id` ASC) USING BTREE,
  CONSTRAINT `categories_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `categories` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `categories_chk_1` CHECK (`level` between 1 and 3),
  CONSTRAINT `categories_chk_2` CHECK (`status` in (_utf8mb4'启用',_utf8mb4'禁用'))
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品分类表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of categories
-- ----------------------------
INSERT INTO `categories` VALUES (1, '电子产品', NULL, 1, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (2, '服装鞋帽', NULL, 1, 2, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (3, '食品饮料', NULL, 1, 3, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (4, '手机', 1, 2, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (5, '电脑', 1, 2, 2, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (6, '男装', 2, 2, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (7, '女装', 2, 2, 2, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (8, '智能手机', 4, 3, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (9, '笔记本电脑', 5, 3, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');

-- ----------------------------
-- Table structure for order_items
-- ----------------------------
DROP TABLE IF EXISTS `order_items`;
CREATE TABLE `order_items`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `order_id` int NULL DEFAULT NULL COMMENT '订单ID',
  `product_id` int NULL DEFAULT NULL COMMENT '商品ID',
  `product_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品名称',
  `product_price` decimal(10, 2) NOT NULL COMMENT '商品单价',
  `quantity` int NOT NULL COMMENT '购买数量',
  `total_price` decimal(12, 2) NOT NULL COMMENT '小计金额',
  `discount` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '单项优惠金额',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `order_id`(`order_id` ASC) USING BTREE,
  INDEX `product_id`(`product_id` ASC) USING BTREE,
  CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `order_items_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `order_items_chk_1` CHECK (`product_price` >= 0),
  CONSTRAINT `order_items_chk_2` CHECK (`quantity` > 0),
  CONSTRAINT `order_items_chk_3` CHECK (`total_price` >= 0),
  CONSTRAINT `order_items_chk_4` CHECK (`discount` >= 0)
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单明细表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of order_items
-- ----------------------------
INSERT INTO `order_items` VALUES (1, 1, 1, 'iPhone 15 Pro Max', 9999.00, 1, 9999.00, 100.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (2, 2, 2, '小米14 Ultra', 5999.00, 1, 5999.00, 0.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (3, 3, 5, '男士羽绒服', 899.00, 2, 1798.00, 0.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (4, 3, 6, '女士羊绒大衣', 2599.00, 1, 2599.00, 200.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (5, 4, 5, '男士羽绒服', 899.00, 1, 899.00, 50.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (6, 5, 6, '女士羊绒大衣', 2599.00, 1, 2599.00, 0.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (7, 6, 2, '小米14 Ultra', 5999.00, 1, 5999.00, 100.00, '2026-07-18 22:26:42');

-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `order_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单编号',
  `user_id` int NULL DEFAULT NULL COMMENT '用户ID',
  `total_amount` decimal(12, 2) NOT NULL COMMENT '订单总金额',
  `discount_amount` decimal(12, 2) NULL DEFAULT 0.00 COMMENT '优惠金额',
  `pay_amount` decimal(12, 2) NOT NULL COMMENT '实付金额',
  `freight` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '运费',
  `pay_method` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '在线支付' COMMENT '支付方式',
  `order_status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '待付款' COMMENT '订单状态',
  `payment_time` timestamp NULL DEFAULT NULL COMMENT '支付时间',
  `delivery_time` timestamp NULL DEFAULT NULL COMMENT '发货时间',
  `receive_time` timestamp NULL DEFAULT NULL COMMENT '收货时间',
  `complete_time` timestamp NULL DEFAULT NULL COMMENT '完成时间',
  `cancel_time` timestamp NULL DEFAULT NULL COMMENT '取消时间',
  `shipping_address` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收货地址',
  `receiver_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收货人姓名',
  `receiver_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收货人电话',
  `remark` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '备注',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `order_no`(`order_no` ASC) USING BTREE,
  INDEX `user_id`(`user_id` ASC) USING BTREE,
  CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `orders_chk_1` CHECK (`total_amount` >= 0),
  CONSTRAINT `orders_chk_2` CHECK (`discount_amount` >= 0),
  CONSTRAINT `orders_chk_3` CHECK (`pay_amount` >= 0),
  CONSTRAINT `orders_chk_4` CHECK (`freight` >= 0),
  CONSTRAINT `orders_chk_5` CHECK (`pay_method` in (_utf8mb4'在线支付',_utf8mb4'货到付款',_utf8mb4'银行转账')),
  CONSTRAINT `orders_chk_6` CHECK (`order_status` in (_utf8mb4'待付款',_utf8mb4'已付款',_utf8mb4'已发货',_utf8mb4'已收货',_utf8mb4'已完成',_utf8mb4'已取消',_utf8mb4'退货中'))
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单主表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders` VALUES (1, 'ORD2024120001', 1, 9999.00, 100.00, 9899.00, 0.00, '在线支付', '已完成', '2024-12-01 10:30:00', '2024-12-02 14:00:00', '2024-12-05 16:20:00', NULL, NULL, '北京市朝阳区XX路1号', '张伟', '13800001001', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (2, 'ORD2024120002', 2, 5999.00, 0.00, 5999.00, 20.00, '在线支付', '已发货', '2024-12-10 14:20:00', '2024-12-11 09:30:00', NULL, NULL, NULL, '上海市浦东新区XX路2号', '李明', '13800001002', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (3, 'ORD2024120003', 3, 3498.00, 200.00, 3298.00, 0.00, '在线支付', '已收货', '2024-12-15 08:45:00', '2024-12-16 16:00:00', '2024-12-18 10:00:00', NULL, NULL, '广州市天河区XX路3号', '王芳', '13800001003', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (4, 'ORD2024120004', 1, 899.00, 50.00, 849.00, 15.00, '货到付款', '已付款', '2024-12-18 11:00:00', NULL, NULL, NULL, NULL, '北京市海淀区XX路4号', '张伟', '13800001001', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (5, 'ORD2024120005', 4, 2599.00, 0.00, 2599.00, 0.00, '在线支付', '待付款', NULL, NULL, NULL, NULL, NULL, '深圳市南山区XX路5号', '陈静', '13800001004', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (6, 'ORD2024120006', 5, 5999.00, 100.00, 5899.00, 30.00, '在线支付', '已完成', '2024-12-05 09:15:00', '2024-12-06 10:00:00', '2024-12-08 14:30:00', NULL, NULL, '杭州市西湖区XX路6号', '刘俊', '13800001005', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');

-- ----------------------------
-- Table structure for products
-- ----------------------------
DROP TABLE IF EXISTS `products`;
CREATE TABLE `products`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `category_id` int NULL DEFAULT NULL COMMENT '分类ID',
  `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品名称',
  `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '商品描述',
  `price` decimal(10, 2) NOT NULL COMMENT '商品价格',
  `stock` int NOT NULL DEFAULT 0 COMMENT '库存数量',
  `sales_volume` int NULL DEFAULT 0 COMMENT '销量',
  `rating` decimal(3, 2) NULL DEFAULT 0.00 COMMENT '评分',
  `brand` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '品牌',
  `supplier` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '供应商',
  `images` json NULL COMMENT '商品图片JSON数组',
  `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '上架' COMMENT '商品状态',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `category_id`(`category_id` ASC) USING BTREE,
  CONSTRAINT `products_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `products_chk_1` CHECK (`price` >= 0),
  CONSTRAINT `products_chk_2` CHECK (`stock` >= 0),
  CONSTRAINT `products_chk_3` CHECK (`sales_volume` >= 0),
  CONSTRAINT `products_chk_4` CHECK (`rating` between 0 and 5),
  CONSTRAINT `products_chk_5` CHECK (`status` in (_utf8mb4'上架',_utf8mb4'下架',_utf8mb4'售罄'))
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品信息表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of products
-- ----------------------------
INSERT INTO `products` VALUES (1, 8, 'iPhone 15 Pro Max', '苹果最新旗舰手机,A17芯片,钛金属边框', 9999.00, 50, 120, 4.80, 'Apple', '苹果中国', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (2, 8, '小米14 Ultra', '徕卡光学镜头,骁龙8 Gen3', 5999.00, 80, 200, 4.60, 'Xiaomi', '小米科技', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (3, 9, 'MacBook Pro 16寸', 'M3 Max芯片,36GB内存,1TB SSD', 24999.00, 20, 45, 4.90, 'Apple', '苹果中国', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (4, 9, '联想 ThinkPad X1', '轻薄商务本,i7处理器,32GB内存', 15999.00, 30, 78, 4.50, 'Lenovo', '联想集团', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (5, 6, '男士羽绒服', '90%白鹅绒,防风防水,-30℃保暖', 899.00, 100, 350, 4.30, '波司登', '波司登服饰', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (6, 7, '女士羊绒大衣', '100%山羊绒,双面呢,经典款', 2599.00, 60, 180, 4.70, '鄂尔多斯', '鄂尔多斯集团', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');

-- ----------------------------
-- Table structure for reviews
-- ----------------------------
DROP TABLE IF EXISTS `reviews`;
CREATE TABLE `reviews`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `product_id` int NULL DEFAULT NULL COMMENT '商品ID',
  `user_id` int NULL DEFAULT NULL COMMENT '用户ID',
  `order_id` int NULL DEFAULT NULL COMMENT '订单ID',
  `order_item_id` int NULL DEFAULT NULL COMMENT '订单明细ID',
  `rating` int NOT NULL COMMENT '评分',
  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '评价内容',
  `images` json NULL COMMENT '评价图片JSON数组',
  `is_anonymous` tinyint(1) NULL DEFAULT 0 COMMENT '是否匿名',
  `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '审核中' COMMENT '审核状态',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `product_id`(`product_id` ASC) USING BTREE,
  INDEX `user_id`(`user_id` ASC) USING BTREE,
  INDEX `order_id`(`order_id` ASC) USING BTREE,
  CONSTRAINT `reviews_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `reviews_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `reviews_ibfk_3` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `reviews_chk_1` CHECK (`rating` between 1 and 5),
  CONSTRAINT `reviews_chk_2` CHECK (`status` in (_utf8mb4'审核中',_utf8mb4'已通过',_utf8mb4'已拒绝'))
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品评价表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of reviews
-- ----------------------------
INSERT INTO `reviews` VALUES (1, 1, 1, 1, 1, 5, '手机非常好,拍照效果惊艳!', NULL, 0, '已通过', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `reviews` VALUES (2, 2, 5, 6, 7, 4, '性能强劲,就是发热有点严重', NULL, 0, '已通过', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `reviews` VALUES (3, 5, 3, 3, 3, 5, '羽绒服很暖和,质量很好', NULL, 0, '已通过', '2026-07-18 22:26:42', '2026-07-18 22:26:42');

-- ----------------------------
-- Table structure for spring_ai_chat_memory
-- ----------------------------
DROP TABLE IF EXISTS `spring_ai_chat_memory`;
CREATE TABLE `spring_ai_chat_memory`  (
  `conversation_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `type` enum('USER','ASSISTANT','SYSTEM','TOOL') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `timestamp` timestamp NOT NULL,
  `sequence_id` bigint NOT NULL,
  INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX`(`conversation_id` ASC, `timestamp` ASC) USING BTREE,
  INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_SEQUENCE_ID_IDX`(`conversation_id` ASC, `sequence_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of spring_ai_chat_memory
-- ----------------------------

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
  `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '电子邮箱',
  `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号码',
  `real_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '真实姓名',
  `gender` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性别',
  `age` int NULL DEFAULT NULL COMMENT '年龄',
  `level` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '普通会员' COMMENT '会员等级',
  `register_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
  `last_login_time` timestamp NULL DEFAULT NULL COMMENT '最后登录时间',
  `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '正常' COMMENT '账户状态',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `username`(`username` ASC) USING BTREE,
  UNIQUE INDEX `email`(`email` ASC) USING BTREE,
  CONSTRAINT `users_chk_1` CHECK (`gender` in (_utf8mb4'男',_utf8mb4'女',_utf8mb4'保密')),
  CONSTRAINT `users_chk_2` CHECK ((`age` >= 0) and (`age` <= 150)),
  CONSTRAINT `users_chk_3` CHECK (`level` in (_utf8mb4'普通会员',_utf8mb4'银牌会员',_utf8mb4'金牌会员',_utf8mb4'钻石会员')),
  CONSTRAINT `users_chk_4` CHECK (`status` in (_utf8mb4'正常',_utf8mb4'冻结',_utf8mb4'注销'))
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (1, 'zhangwei', 'zhangwei@email.com', '13800001001', '张伟', '男', 28, '金牌会员', '2024-01-15 10:00:00', '2024-12-20 14:30:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `users` VALUES (2, 'liming', 'liming@email.com', '13800001002', '李明', '男', 35, '钻石会员', '2023-06-20 09:00:00', '2024-12-21 09:15:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `users` VALUES (3, 'wangfang', 'wangfang@email.com', '13800001003', '王芳', '女', 26, '银牌会员', '2024-03-10 16:20:00', '2024-12-19 20:00:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `users` VALUES (4, 'chenjing', 'chenjing@email.com', '13800001004', '陈静', '女', 32, '普通会员', '2024-08-05 11:00:00', '2024-12-18 11:30:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `users` VALUES (5, 'liujun', 'liujun@email.com', '13800001005', '刘俊', '男', 41, '金牌会员', '2023-11-01 08:30:00', '2024-12-20 16:45:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');

4、代码

Maven 依赖 (pom.xml)

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<!--AI start-->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-chat-memory-repository-jdbc -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
<!--AI end-->
<!--向量存储star-->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
</dependency>
<!-- 用于读取 PDF、Word 等各类文档 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<!--向量存储end-->

<!-- Spring Boot JDBC Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- MySQL Connector -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webmvc-test</artifactId>
    <scope>test</scope>
</dependency>
<!--工具-->
<!-- Source: https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>${fastjson2.version}</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>${hutool-all.version}</version>
</dependency>

配置文件 (application.yml)

复制代码
spring:
  application:
    name: text-sql-demo
  ai:
    ollama:
      base-url: http://127.0.0.1:11434
      chat:
        model: qwen3.5:4b # 模型
        temperature: 0.1 # 低温度让 SQL 生成更确定、更准确。
        top-p: 0.7 # 概率
        think: true # 是否思考
      embedding:
        model: nomic-embed-text:latest # embedding 模型
    chat:
      memory:
        repository:
          jdbc:
            initialize-schema: always
    vectorstore:
      qdrant:
        host: 127.0.0.1 # 服务器地址
        port: 6334 # gRPC 端口
        collection-name: rag_knowledge # 集合名称
        initialize-schema: true # 自动创建集合
  datasource:
    url: jdbc:mysql://localhost:3306/text_sql?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 15 
      minimum-idle: 5 
      idle-timeout: 600000 
      max-lifetime: 1800000 
      connection-test-query: SELECT 1 
  servlet:
    encoding:
      charset: UTF-8

关键参数说明:

  • initialize-schema: true:让 Spring AI 自动创建 Qdrant 集合,注意:这在 2.0 版本中默认是关闭的,需要显式开启

temperature: 0.1:低温度让 SQL 生成更确定、更准确

数据库元数据抽取器

从数据库中抽取表结构信息,后续会存入向量库供 RAG 检索。

复制代码
package com.ybw.service;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.ybw.entity.ColumnInfo;
import com.ybw.entity.ForeignKeyInfo;
import com.ybw.entity.TableMetadata;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
@Slf4j
public class EnhancedDatabaseMetadataService {


    private final JdbcClient jdbcClient;
    private final VectorStore vectorStore;
    //数据来源
    private final String DATA_SOURCE_KEY = "data_source";
    private final String DATA_SOURCE_VALUE = "mysql_text_sql";

    /**
     * 提取所有表的结构信息,每个表生成一个 Document 用于向量化存储
     */
    public List<Document> extractEnhancedTableMetadata() {
        try {
            //1、MySQL: 获取所有表名
            List<String> tables = jdbcClient.sql(
                            "SELECT table_name FROM information_schema.tables " +
                                    "WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE'")
                    .query(String.class)
                    .list();

            //2、过滤掉qdrant已存在的表
            Set<String> existingTableNames = getQdrantExistingTableNames();
            List<String> newTables = tables.stream()
                    .filter(t -> !existingTableNames.contains(t))
                    .toList();

            log.info("Found {} tables in database, {} new ({} already in Qdrant)",
                    tables.size(), newTables.size(), tables.size() - newTables.size());
            //3、只对新增的表构建Document
            return buildDocument(newTables);
        } catch (Exception e) {
            log.error("Error extracting table metadata", e);
        }
        return new ArrayList<>();
    }

    /**
     * 查询 Qdrant 中已索引的表名集合
     */
    private Set<String> getQdrantExistingTableNames() {
        try {
            // 使用通用查询检索所有表元数据文档,topK 设置较大值确保覆盖全部表
            List<Document> existingDocs = vectorStore.similaritySearch(
                    SearchRequest.builder()
                            .query("表名 列信息 字段")
                            .topK(10000)
                            .similarityThreshold(0.0)
                            .filterExpression(new FilterExpressionBuilder().eq(DATA_SOURCE_KEY, DATA_SOURCE_VALUE).build())
                            .build()
            );
            Set<String> tableNames = existingDocs.stream()
                    .map(doc -> (String) doc.getMetadata().get("table_name"))
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());
            log.info("Qdrant already has {} indexed tables: {}", tableNames.size(), tableNames);
            return tableNames;
        } catch (Exception e) {
            log.warn("Failed to query Qdrant for existing tables, will index all", e);
            return Set.of();
        }
    }

    /**
     * 构建Document
     *
     * @param tables 表名列表
     * @methodName: buildDocument
     * @return: java.util.List<org.springframework.ai.document.Document>
     * @author: ybw
     * @date: 2026/7/19
     **/
    private List<Document> buildDocument(List<String> tables) {
        //1、如果没有表,则返回空列表
        if (CollectionUtil.isEmpty(tables)) {
            return new ArrayList<>();
        }
        List<Document> documents = new ArrayList<>();
        tables.forEach(tableName -> {
            try {
                //2、获取表结构信息
                TableMetadata tableMeta = getTableMetadata(tableName);
                log.info("Indexed table: {},metadata: {} )",
                        tableName, JSON.toJSONString(tableMeta));
                //2.1 序列化为 JSON 存入 metadata
                String metadataJson = JSON.toJSONString(tableMeta);
                //2.2 构建用于向量检索的自然语言描述
                String content = buildTableDescription(tableName, tableMeta.tableComment(), tableMeta.columns(), tableMeta.foreignKeys());
                //3、 构建 Document 并存入 documents
                Map<String, Object> metadata = Map.of("metadata", metadataJson, "table_name", tableName, DATA_SOURCE_KEY, DATA_SOURCE_VALUE);
                Document doc = new Document(content, metadata);
                documents.add(doc);
            } catch (Exception e) {
                log.error("Error processing table: {}", tableName, e);
            }
        });
        return documents;
    }


    /**
     * 获取表结构信息
     *
     * @param tableName 表名
     * @methodName: getTableMetadata
     * @return: com.ybw.entity.TableMetadata
     * @author: ybw
     * @date: 2026/7/19
     **/
    public TableMetadata getTableMetadata(String tableName) {
        //1、 MySQL: 获取表注释
        String tableComment = jdbcClient.sql(
                        "SELECT table_comment FROM information_schema.tables " +
                                "WHERE table_schema = DATABASE() AND table_name = ?")
                .param(tableName)
                .query(String.class)
                .optional()
                .orElse("");

        //2、获取列信息(MySQL 版本)
        List<ColumnInfo> columns = jdbcClient.sql(
                        """
                                SELECT 
                                    column_name,
                                    data_type,
                                    is_nullable,
                                    column_default,
                                    column_comment
                                FROM information_schema.columns
                                WHERE table_schema = DATABASE() 
                                    AND table_name = ?
                                ORDER BY ordinal_position
                                """)
                .param(tableName)
                .query((rs, rowNum) -> new ColumnInfo(
                        rs.getString("column_name"),
                        rs.getString("data_type"),
                        rs.getString("is_nullable"),
                        rs.getString("column_default"),
                        rs.getString("column_comment")
                ))
                .list();

        //3、获取外键关系(MySQL 版本)
        List<ForeignKeyInfo> foreignKeys = jdbcClient.sql(
                        """
                                SELECT
                                    kcu.column_name,
                                    kcu.referenced_table_name,
                                    kcu.referenced_column_name
                                FROM information_schema.key_column_usage kcu
                                WHERE kcu.table_schema = DATABASE()
                                    AND kcu.table_name = ?
                                    AND kcu.referenced_table_name IS NOT NULL
                                """)
                .param(tableName)
                .query((rs, rowNum) -> new ForeignKeyInfo(
                        rs.getString("column_name"),
                        rs.getString("referenced_table_name"),
                        rs.getString("referenced_column_name")
                ))
                .list();

        //4、构建 TableMetadata 并创建 Document
        return new TableMetadata(
                tableName, tableComment, columns, foreignKeys);


    }


    /**
     * 构建表的自然语言描述,用于向量相似度检索
     */
    private String buildTableDescription(String tableName, String tableComment,
                                         List<ColumnInfo> columns, List<ForeignKeyInfo> foreignKeys) {
        StringBuilder sb = new StringBuilder();
        sb.append("表名: ").append(tableName);
        if (tableComment != null && !tableComment.isEmpty()) {
            sb.append(" (").append(tableComment).append(")");
        }
        sb.append("\n");

        sb.append("列信息:\n");
        for (ColumnInfo col : columns) {
            sb.append("  - ").append(col.name())
                    .append(": ").append(col.type());
            if ("NO".equals(col.nullable())) {
                sb.append(" NOT NULL");
            }
            if (col.defaultValue() != null) {
                sb.append(" 默认值=").append(col.defaultValue());
            }
            if (col.comment() != null && !col.comment().isEmpty()) {
                sb.append(" -- ").append(col.comment());
            }
            sb.append("\n");
        }

        if (!foreignKeys.isEmpty()) {
            sb.append("外键关系:\n");
            for (ForeignKeyInfo fk : foreignKeys) {
                sb.append("  - ").append(fk.columnName())
                        .append(" -> ").append(fk.referencedTable())
                        .append(".").append(fk.referencedColumn())
                        .append("\n");
            }
        }

        return sb.toString();
    }
}

RAG 元数据索引器

将数据库表结构向量化后存入 Qdrant,供后续检索。

复制代码
package com.ybw.config;

import cn.hutool.core.collection.CollectionUtil;
import com.ybw.service.EnhancedDatabaseMetadataService;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@Slf4j
@RequiredArgsConstructor
public class MetadataIndexer {

    private final VectorStore vectorStore;
    private final EnhancedDatabaseMetadataService metadataService;

    /**
     * 启动时,将表结构索引到向量库
     *
     * @methodName: init
     * @return: void
     * @author: ybw
     * @date: 2026/7/19
     **/
    @PostConstruct
    public void init() {
        indexMetadata();
    }

    /**
     * 启动时或手动调用,将表结构索引到向量库
     */
    public void indexMetadata() {
        //1、获取表结构信息
        List<Document> documents = metadataService.extractEnhancedTableMetadata();
        if (CollectionUtil.isEmpty(documents)) {
            log.info("没有新表需要保存到向量数据库");
            return;
        }
        //2、将表结构信息保存到向量库
        vectorStore.add(documents);
        log.info("Indexed {} tables to Qdrant", documents.size());
    }
}

数据库元数据 Advisor(核心 RAG 逻辑)

Advisor 在每次请求前拦截,从 Qdrant 检索相关表结构,注入到 Prompt 中。

复制代码
package com.ybw.advisor;

import org.jspecify.annotations.NonNull;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.content.Content;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.Ordered;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class DatabaseMetadataAdvisor implements BaseAdvisor {

    private static final String SYSTEM_TEMPLATE = """
            你是一个 SQL 专家。请根据下面的数据库表结构,生成可执行的 SQL 语句来回答用户问题。
            只生成 SELECT 查询,不要执行 DDL/DML 操作。
            
            ===可用的表结构===
            {table_schemas}
            """;

    private final VectorStore vectorStore;

    public DatabaseMetadataAdvisor(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    @Override
    public ChatClientRequest before(@NonNull ChatClientRequest request, @NonNull AdvisorChain chain) {
        //1、从用户消息中提取查询文本
        String userQuery = extractUserQuery(request);

        //2、从 Qdrant 检索相关表结构(topK=3)
        List<Document> relevantTables = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(userQuery)
                        .topK(3)
                        .similarityThreshold(0.5)
                        .build()
        );

        //3、构建表结构信息
        String tableSchemas = relevantTables.stream()
                .map(doc -> doc.getMetadata().get("metadata").toString())
                .collect(Collectors.joining("\n\n"));

        //4、渲染系统提示
        String systemText = new PromptTemplate(SYSTEM_TEMPLATE)
                .render(Map.of("table_schemas", tableSchemas));

        //5、更新请求
        return request.mutate()
                .prompt(request.prompt().augmentSystemMessage(systemText))
                .build();
    }

    /**
     * 从用户消息中提取查询文本
     *
     * @param request 请求
     * @methodName: extractUserQuery
     * @return: java.lang.String
     * @author: ybw
     * @date: 2026/7/19
     **/
    private String extractUserQuery(ChatClientRequest request) {
        return request.prompt().getInstructions().stream()
                .filter(msg -> msg instanceof UserMessage)
                .map(Content::getText)
                .findFirst()
                .orElse("");
    }

    @Override
    public ChatClientResponse after(@NonNull ChatClientResponse response, @NonNull AdvisorChain chain) {
        return response;
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

SQL 执行工具(Tool Calling)

让 LLM 可以调用工具执行生成的 SQL 并返回结果。Spring AI 2.0 使用 @Tool 注解定义工具。

复制代码
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 运行 SQL 查询工具
 *
 * @author ybw
 * @version V1.0
 * @className RunSqlQueryTool
 * @date 2026/7/19
 **/
@Component
@RequiredArgsConstructor
@Slf4j
public class RunSqlQueryTool {

    private final JdbcClient jdbcClient;

    @Tool(description = "执行 SQL 查询并返回结果,以 CSV 格式输出")
    public String runQuery(RunSqlQueryRequest request) {
        try {
            //1、执行sql查询
            String sql = request.query();
            log.info("sql: {}", sql);
            List<Map<String, Object>> rows = jdbcClient.sql(sql)
                    .query()
                    .listOfRows();

            if (rows.isEmpty()) {
                return "查询结果为空";
            }
            //2、简单格式化为 CSV
            StringBuilder sb = new StringBuilder();
            //2.1 表头
            sb.append(String.join(",", rows.getFirst().keySet())).append("\n");
            //2.2 数据行
            rows.forEach(row -> {
                sb.append(row.values().stream()
                                .map(String::valueOf)
                                .collect(Collectors.joining(",")))
                        .append("\n");
            });
            return sb.toString();
        } catch (Exception e) {
            return "SQL 执行错误: " + e.getMessage();
        }
    }

    public record RunSqlQueryRequest(String query) {
    }
}

ChatClient 配置

复制代码
package com.ybw.config;

import com.ybw.advisor.DatabaseMetadataAdvisor;
import com.ybw.util.RunSqlQueryTool;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.ollama.api.OllamaChatOptions;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * 配置 ChatClient
 *
 * @author ybw
 * @version V1.0
 * @className ChatClientConfig
 * @date 2026/6/29
 **/
@Configuration
public class ChatClientConfig {



    /**
     * 工具大模型:绑定工具
     *
     * @param builder     构建 ChatClient
     * @param vectorStore 向量库
     * @param toolService 工具服务
     * @methodName: toolChatClient
     * @return: org.springframework.ai.chat.client.ChatClient
     * @author: ybw
     * @date: 2026/7/8
     **/
    @Bean("toolChatClient")
    ChatClient toolChatClient(ChatClient.Builder builder, VectorStore vectorStore, RunSqlQueryTool toolService) {
        OllamaChatOptions.Builder ollamaChatOptions = OllamaChatOptions.builder()
                .model("qwen3.5:4b")
                .disableThinking()
                .temperature(0.4);
        return builder
                .defaultSystem("你是 SQL 专家")
                //绑定工具
                .defaultTools(toolService)
                .defaultAdvisors(new DatabaseMetadataAdvisor(vectorStore))
                .defaultOptions(ollamaChatOptions)
                .build();
    }

}

测试

复制代码
package com.ybw.service;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @author ybw
 * @version V1.0
 * @className TextToSqlTest
 * @date 2026/7/19
 **/
@SpringBootTest
@Slf4j
public class TextToSqlTest {
    @Resource(name = "toolChatClient")
    private ChatClient chatClient;

    /**
     * 查询所有用户订单数量
     */
    @Test
    public void testTextToSql() {
        String question = "查询所有用户订单数量";
        String result = chatClient.prompt()
                .user(question)
                .call()
                .content();
        log.info("result: {}", result);
    }
    /**
     * 查询订单总金额超过5000的订单
     */
    @Test
    public void testTextToSql2() {
        String question = "查询订单总金额超过5000的订单";
        String result = chatClient.prompt()
                .user(question)
                .call()
                .content();
        log.info("result: {}", result);
    }

    /**
     * 查看张伟的所有订单
     */
    @Test
    public void testTextToSql3() {
        String question = "查看张伟的所有订单";
        String result = chatClient.prompt()
                .user(question)
                .call()
                .content();
        log.info("result: {}", result);
    }

    /**
     * 统计每个商品分类下的商品数量
     */
    @Test
    public void testTextToSql4() {
        String question = "统计每个商品分类下的商品数量";
        String result = chatClient.prompt()
                .user(question)
                .call()
                .content();
        log.info("result: {}", result);
    }

}

5、补充

阿里相关框架

Spring AI Alibaba中的Data Agent

  • 基于 Spring AI Alibaba 的自然语言转 SQL 项目,让您可以直接使用自然语言查询数据库,无需编写复杂的 SQL。
相关推荐
love530love13 小时前
【笔记】AutoClaw NSIS 安装器卡死、进程杀不掉、目录删不了?我是这么解决的
人工智能·windows·笔记·agent
Mandy的名字被占用了13 小时前
晨风AI+知识付费系统|学练考全闭环,重构教育变现新模式
人工智能·后端
步步为营DotNet13 小时前
Avalonia 11.3 本地离线AI图像识别绑定Minimal API AI推理网关
人工智能
满怀冰雪13 小时前
06-自动微分入门:用 Paddle 计算梯度
人工智能·python·深度学习·paddle
架构源启13 小时前
文档接入与智能解析:基于 Spring AI 1.1.x 的多格式解析、版面理解与结构化抽取
java·人工智能·spring
李昊哲小课14 小时前
GLM 多技术栈集成完整教程
人工智能·智能体
浩哥学JavaAI15 小时前
2026年最新AI agent面试(10)_通信与行业动态
人工智能·面试·职场和发展
新知图书15 小时前
7.4 测试与发布(一键生成PPT智能体开发)
人工智能·agent·ai agent·智能体·扣子
糖果店的幽灵15 小时前
【langgraph 从入门到精通graphApi 篇】Checkpoint 持久化与状态管理
人工智能·langgraph