作为多年供应链 / WMS 方向 Java 后端开发,结合我过往仓储业务背景,花费两天独立搭建一套轻量化私有化 RAG 问答系统:SpringBoot 3.5.16+ SpringAI1.0 + Ollama 本地大模型 + MySQL9.7 原生向量,单库同时持久化业务文档原文与 Embedding 向量,无需第三方向量中间件,适配企业内网离线场景,最终实现仓储规则自然语言问答,下面完整记录全流程实现、踩坑与架构选型思考。
一、项目整体架构
1. 架构流程图(自行配图)
前端 SSE 对话页面 → SpringBoot 后端(SpringAI 核心层) → Ollama 本地大模型(对话大模型 + Embedding 向量化模型) → MySQL9.7(存储业务文档、文本、向量)
2. 各层职责拆解
- 前端层:极简对话页面,基于 SSE 长连接实现打字机流式输出,解决一次性加载长回答卡顿问题;
- 后端服务层
- SpringAI 封装 Ollama 调用,统一管理对话请求、文本切片、Embedding 向量生成;
- RAG 核心逻辑:用户提问→问题向量化→MySQL 向量相似度检索匹配知识库文档→拼接上下文 Prompt 送入大模型→流式返回答案;
- 大模型层:Ollama 本地离线部署,全程不调用任何付费云端 API,私有化无网络依赖;
- 存储层:MySQL9.7 原生
VECTOR字段存储向量,TEXT 字段存原始业务文档,结构化字段记录文档分类、创建时间,单库保证原文与向量事务一致性。
3. 业务场景定位
面向仓储供应链内部员工,知识库录入仓库管理制度、差旅报销标准、公司基础信息、售后流程等文档;员工通过自然语言提问,自动检索对应业务规则给出回答,替代翻阅零散 Word 文档,提升内部咨询效率。
二、核心技术选型与选型权衡(拔高核心,面试重点)
1. 向量存储:为什么选用 MySQL9.7,放弃 Milvus/Chroma?
- 优势
- 零额外运维:现有业务均使用 MySQL,无需部署、维护独立向量服务,降低小团队运维成本;
- 数据强一致性:业务文档、文本、向量存在同一张数据表,新增 / 修改 / 删除可通过 MySQL 事务原子操作,彻底规避「向量库与业务库数据不同步」的经典 RAG 痛点;
- 学习成本低:开发无需额外掌握向量库专属 API,全部使用标准 SQL 完成向量检索;
- 适用边界(客观说明短板,体现架构思考)MySQL 仅支持精确 KNN 全表距离计算,无 HNSW、IVF 等 ANN 近似向量索引,仅适合10 万条以内文档的内部后台系统;若面向 C 端、百万级海量向量高并发检索,仍需 Milvus、PGVector 等专业向量引擎。
- 对比 PGVector:团队技术栈统一 MySQL,无需额外搭建 PostgreSQL 环境,优先选用 MySQL 原生向量能力。
2. AI 框架:SpringAI 2.0
Spring 官方 AI 集成框架,统一封装各大模型厂商 API,本次对接本地 Ollama;踩坑重点:2.0 版本发生破坏性 API 重构,大量旧类弃用、包路径变更,网上多数教程为旧版本,存在大量兼容问题。
3. 本地大模型:Ollama
离线私有化部署,本地完成对话、文本 Embedding 向量化,无 API 调用费用,适配企业内网、涉密场景,无需申请任何模型 Key。
4. 流式输出:SSE 服务端推送
区别于普通同步接口一次性返回完整文本,采用 SSE 长连接分段推送大模型输出内容,前端实现打字机实时展示效果,提升交互体验。
三、核心功能实现代码
3.1 MySQL9.7 向量数据表结构(附完整 SQL,自行截图)
支持存储文档原文、分类、向量、创建时间,VECTOR (768) 适配主流 Embedding 模型 768 维向量:
CREATE TABLE IF NOT EXISTS knowledge_source ( id BIGINT AUTO_INCREMENT PRIMARY KEY, category VARCHAR(64) COMMENT '数据分类,如 wms / finance / company', content TEXT NOT NULL COMMENT '原始知识文本', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS vector_store ( id VARCHAR(64) PRIMARY KEY, content TEXT, metadata JSON, embedding TEXT -- 向量以 JSON 数组文本存储,如 [0.12,-0.34,...] ); INSERT INTO knowledge_source (category, content) VALUES ('wms', '商品库存低于安全库存时,必须在24小时内提交补货申请'), ('wms', '入库货物外包装破损、水渍,需生成质检异常单,禁止上架'), ('wms', '拣货作业优先匹配就近库位,同一订单多商品合并拣货,减少动线'), ('wms', '仓库A01存放包装耗材,B02存放大件货物,不可混放'), ('finance', '员工差旅费住宿单日上限300元,超出部分财务不予报销'), ('finance', '采购增值税发票必须匹配对应入库单,票货不一致直接驳回审核'), ('finance', '仓储耗材采购单次金额超过5000元,需要部门经理审批'), ('aftersale', '客户签收7天内商品无损坏,支持无理由退货'), ('aftersale', '缺货订单需当日联系客户,选择补发或全额退款二选一'), ('company', '公司全称:北京智联仓储科技有限公司,成立于2020年,总部位于北京海淀区'), ('company', '公司目前员工总数350人,其中仓储运营部120人、技术研发部80人...'), ('company', 'CEO张伟、CTO李明、COO王芳,电话:010-88886666...'), ('company', '公司在上海、广州、成都设有分公司...');3.2 Maven 关键依赖(踩坑点:SpringAI 依赖下载失败解决方案)
SpringAI 官方仓库国内镜像同步延迟高,提供阿里云镜像加速配置,解决依赖拉取超时、缺失问题,目前因为springAi2.0.0刚上市不久,aliyun服务器目前针对这个版本的jar包不全,所以暂时用旧版稳定版,完整 pom 核心依赖片段如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.5.16</version> <relativePath/> </parent> <groupId>com.my</groupId> <artifactId>springAi</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Ollama-RAG-Demo</name> <properties> <java.version>21</java.version> <spring-ai.version>1.0.0</spring-ai.version> </properties> <dependencies> <!-- Spring Web(Servlet 容器,提供 REST 与 SSE 适配) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- WebFlux:提供 Reactor 响应式适配,使 Controller 能直接返回 Flux<String> 做 SSE 流式输出 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- JDBC:JdbcTemplate + 数据源自动配置,用于自定义 MySQL 向量库 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- MySQL 9.7 驱动(原生 VECTOR 类型支持) --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency> <!-- Spring AI BOM 统一管理版本 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-ollama</artifactId> </dependency> <!-- Spring AI 公共模块:VectorStore 抽象、SearchRequest、Document、EmbeddingModel 等 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-vector-store</artifactId> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> <!-- 通过 BOM 引入 Spring AI 1.0.0 各模块版本,避免逐个指定 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>3.3 SpringAI 整合 Ollama 配置
application.yml Ollama 模型地址、超时配置; 完整 Config 配置类:OllamaChatModel、OllamaEmbeddingModel Bean 注入。
3.4 RAG 核心业务逻辑
- 文档入库流程:原始文本分段切片 → 调用 Embedding 模型生成向量 → SQL 动态插入 MySQL,文本与向量同步落库;
- 问答检索流程:用户提问 → 问题向量化 → MySQL 向量相似度召回匹配文档 → 组装上下文 Prompt 传入对话模型 → SSE 流式返回结果。
package com.my.springai.controller; import com.my.springai.dto.AiQueryDTO; import com.my.springai.service.RagService; import com.my.springai.vectorstore.MysqlVectorStore; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import java.util.LinkedHashMap; import java.util.Map; @RestController @RequestMapping("/ollama/rag") @RequiredArgsConstructor public class RagController { private final RagService ragService; private final MysqlVectorStore mysqlVectorStore; /** 加载本地业务规则文档,分片向量化后存入 MySQL */ @GetMapping("/load") public String loadDoc() { return ragService.loadDoc(); } /** 同步问答 */ @PostMapping("/chat") public String ragChat(@RequestBody AiQueryDTO dto) { return ragService.chat(dto.getQuestion()); } /** SSE 流式问答,前端 sse-test.html 调用 */ @GetMapping(value = "/stream/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamChat(@RequestParam String question) { return ragService.streamChat(question); } /** 手动上传单据文本,分片入库 */ @PostMapping("/upload/doc") public String uploadDoc(@RequestParam String docText) { return ragService.uploadDoc(docText); } /** 从 knowledge_source 表重新同步数据到向量库 */ @GetMapping("/sync") public String syncFromDb() { return ragService.syncFromDb(); } /** 诊断接口:查看向量库状态 */ @GetMapping("/status") public Map<String, Object> status() { Map<String, Object> result = new LinkedHashMap<>(); result.put("向量库文档总数", mysqlVectorStore.count()); result.put("提示", "如果总数为 0,请先调用 GET /ollama/rag/sync 或重启应用"); return result; } }
3.5 SSE 流式对话 Controller
标准 SSE 接口实现,分段推送大模型输出,前端持续监听打印回答内容,附带跨域、编码处理逻辑。
最终前端页面展示的效果图如下:
现有短板
- MySQL 无 ANN 向量索引,十万条以上文档向量检索速度大幅下降;
- 文本切片规则简单,未做递归分割、重叠分块优化,长文档召回精度一般;
- 无向量缓存,重复提问会重复计算向量,存在性能浪费;
- 未实现文档增量更新、向量批量刷新能力。
后续迭代优化方向
- 海量数据场景:拆分存储架构,MySQL 存业务原文,引入 Milvus 做高性能向量检索;
- 检索优化:增加 Redis 缓存高频问题向量结果,提升重复问答响应速度;
- 文本处理:完善递归文本分割、关键词增强检索,提升 RAG 召回准确率;
- 功能扩展:接入文档上传解析(PDF/Word 自动切片入库)、多轮对话记忆、角色权限控制。
七、总结
- 对于中小企业内部低并发知识库场景,MySQL9.7 原生向量完全可以替代独立向量数据库,大幅简化系统架构,降低运维成本;
- SpringAI 大幅降低 Java 后端接入大模型的开发门槛,但版本迭代快,2.0 存在大量不兼容改动,开发时需要对照官方最新文档;
- 私有化 Ollama+MySQL 的 RAG 方案,适合内网、无外网环境的企业业务 AI 落地,是传统后端向 AI 业务拓展低成本实践路径;
- 本次实战完整覆盖「文档存储→向量化→向量检索→大模型问答→流式前端交互」整条 RAG 链路,弥补传统 Java 后端缺少 AI 落地实战经验的短板,相关技术、选型思路、踩坑记录均可用于面试沟通。
- 完整项目开源地址:https://gitee.com/danruoshui/springai-ollama-rag.git; https://github.com/316959108/springai-ollama-rag.git
- 配套资料:MySQL9.7 向量官方文档、SpringAI2.0 官方参考文档;
- 欢迎交流后端 AI 落地相关问题。