发布时间:2026/9/2 16:40:55
Java程序员的RAG入门指南:手写Spring Boot实现,附收藏技巧! 本文专为3年以上Java后端工程师设计以Spring Boot 2.7Java 8环境为例手写实现RAG检索增强生成全链路包含切分、向量化、检索及生成步骤。核心代码已实测通过不涉及LangChain4j教程或向量数据库评测。文章提供详细实现步骤适合想了解RAG原理的小白或程序员学习并附有收藏技巧助您快速掌握大模型技术。写给谁3年以上 Java 后端想上手 RAG 但被市面上的 Python 教程劝退。这篇是什么从切分、向量化、检索到生成全链路手写Spring Boot 2.7 Java 8 兼容核心代码实测通过。这篇不是什么不是 LangChain4j 教程不是向量数据库评测。0.为什么写这篇市面上的 RAG 教程几乎都是 Python 的LangChain、LlamaIndex、FAISS……看的时候很爽合上电脑还是不知道 Java 怎么落地。我在传统行业做 Java 后端环境是 Java 8 Spring Boot没有 GPU没有 K8s。我的诉求很简单用最少的依赖先把 RAG 的原理完整跑通一遍再考虑框架和向量库。这篇文章就是我的完整过程代码全部在本地跑过核心算法部分在 JDK 8 上编译运行验证通过。你也跟着做一遍大概一个周末能跑完。1.RAG 是什么一句话先检索再生成。大模型的知识截止时间是固定的你问它没学过的东西它要么说不知道要么一本正经地编幻觉。RAGRetrieval-Augmented Generation检索增强生成的做法是回答问题之前先从一个知识库里把相关片段检索出来连同问题一起喂给模型让模型带着资料答题。流程四步文档 → 切分成片段 → 向量化入库建索引问题 → 向量化 → 和库里的向量算相似度 → 取 TopK检索参考资料 问题 → 大模型 → 答案生成难点只有一个相关怎么度量。答案是用向量——把文本变成一串数字embedding语义相近的文本向量方向也接近。检索就是算余弦相似度。2.技术选型三个反直觉的决定决定为什么模型用 Ollama 本地跑隐私、免费、离线。Ollama 是开源工具一条命令拉模型不需要账号不需要 GPU7B 模型 CPU 也能跑慢一点而已第一版不用向量数据库先用内存 手写余弦相似度把原理跑通。向量数据库pgvector/Milvus是规模化问题不是原理问题Java 8 Spring Boot 2.7传统行业环境。等原理通了想升级 Java 17 Spring Boot 3代码基本不用改3.完整实现3.1 项目结构rag-demo/ ├── pom.xml └── src/main/ ├── java/com/example/rag/ │ ├── RagApplication.java 启动类 │ ├── OllamaClient.java 调 Ollamaembedding chat │ ├── TextSplitter.java 文档切分器纯逻辑已实测 │ ├── VectorStore.java 内存向量库 余弦相似度纯逻辑已实测 │ ├── RagService.java 编排建索引 / 检索 / 生成 │ └── RagController.java REST 接口 └── resources/application.yml3.2 pom.xml只有 web 一个依赖?xmlversion1.0encodingUTF-8?projectxmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.7.18/versionrelativePath//parentgroupIdcom.example/groupIdartifactIdrag-demo/artifactIdversion0.0.1-SNAPSHOT/versionpropertiesjava.version8/java.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/project3.3 TextSplitter文档切分器已实测切分是整个链路里最容易被忽视、影响却最大的一环。切太粗检索到的大段文本里相关信息被稀释切太细单个片段语义不完整。我用固定窗口 重叠package com.example.rag;importjava.util.ArrayList;importjava.util.List;/ * 文档切分器按字符窗口切分带重叠窗口 * 避免把一句话拦腰截断导致检索到的片段读不通。 */ public class TextSplitter{/ chunkSize 窗口大小overlap 相邻窗口重叠字符数 */ public ListStringsplit(String text, int chunkSize, int overlap){ListStringchunksnew ArrayList();int start0;int lentext.length();while(startlen){int endMath.min(start chunkSize, len);chunks.add(text.substring(start, end));if(endlen){break;}startMath.max(end - overlap, start 1);// 至少前进1个字符防止死循环}returnchunks;}}3.4 VectorStore内存向量库 余弦相似度已实测package com.example.rag;importjava.util.ArrayList;importjava.util.Comparator;importjava.util.List;/ * 内存向量库存(文本, 向量)对按余弦相似度检索 TopK。 */ public class VectorStore{public static class Chunk{public final String text;public final ListDoublevector;public Chunk(String text, ListDoublevector){this.texttext;this.vectorvector;}}public static class Hit{public final String text;public final double score;public Hit(String text, double score){this.texttext;this.scorescore;}}private final ListChunkchunksnew ArrayList();public void add(String text, ListDoublevector){chunks.add(new Chunk(text, vector));}public voidclear(){chunks.clear();}public intsize(){returnchunks.size();}/ 按余弦相似度检索 TopK相似度高的排前面 */ public ListHitsearch(ListDoublequery, int topK){ListHithitsnew ArrayList();for(Chunk c:chunks){hits.add(new Hit(c.text, cosine(query, c.vector)));}hits.sort(Comparator.comparingDouble((Hit h)-h.score).reversed());returnhits.size()topK ? hits.subList(0, topK):hits;}private double cosine(ListDoublea, ListDoubleb){if(a.size()!b.size()){// 维度不一致说明 embedding 模型被换过但旧向量没清空。 // 返回0不相似生产环境应记录告警并主动清库。return0;}double dot0, na0, nb0;for(int i0;ia.size();i){dota.get(i)* b.get(i);naa.get(i)* a.get(i);nbb.get(i)* b.get(i);}double denomMath.sqrt(na)* Math.sqrt(nb);returndenom0?0:dot / denom;}}3.5 OllamaClient调 Ollama 的 HTTP 客户端用 Spring 自带的 RestTemplate零额外依赖。两个接口/api/embed 算向量/api/chat 对话。package com.example.rag;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;importorg.springframework.web.client.RestTemplate;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.List;importjava.util.Map;Component public class OllamaClient{private final RestTemplate restnew RestTemplate();Value(${ollama.base-url:http://localhost:11434})private String baseUrl;Value(${ollama.embed-model:nomic-embed-text})private String embedModel;Value(${ollama.chat-model:qwen2.5:7b})private String chatModel;/ 计算文本的 embedding 向量 */ public ListDoubleembed(String text){MapString, Objectbodynew HashMap();body.put(model, embedModel);body.put(input, text);SuppressWarnings(unchecked)MapString, Objectresprest.postForObject(baseUrl /api/embed, body, Map.class);ListListDoubleembeddings(ListListDouble)resp.get(embeddings);returnembeddings.get(0);}/ 对话补全 */ public String chat(String systemPrompt, String userPrompt){MapString, Objectbodynew HashMap();body.put(model, chatModel);body.put(stream,false);MapString, StringsysMsgnew HashMap();sysMsg.put(role,system);sysMsg.put(content, systemPrompt);MapString, StringuserMsgnew HashMap();userMsg.put(role,user);userMsg.put(content, userPrompt);ListMapString, Stringmessagesnew ArrayList();messages.add(sysMsg);messages.add(userMsg);body.put(messages, messages);SuppressWarnings(unchecked)MapString, Objectresprest.postForObject(baseUrl /api/chat, body, Map.class);MapString, Objectmessage(MapString, Object)resp.get(message);return(String)message.get(content);}}3.6 RagService把链路串起来package com.example.rag;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.util.List;Service public class RagService{private static final int CHUNK_SIZE500;// 字符 private static final int OVERLAP80;// 重叠 private static final int TOP_K3;Autowired private OllamaClient ollama;private final TextSplitter splitternew TextSplitter();private final VectorStore storenew VectorStore();/ 建索引切分 向量化 入库 */ public void index(String document){store.clear();for(String chunk:splitter.split(document, CHUNK_SIZE, OVERLAP)){store.add(chunk, ollama.embed(chunk));}}/ 问答检索 TopK 拼上下文 生成 */ public String ask(String question){ListDoubleqVecollama.embed(question);ListVectorStore.Hithitsstore.search(qVec, TOP_K);StringBuilder contextnew StringBuilder();for(int i0;ihits.size();i){context.append([).append(i 1).append(] ).append(hits.get(i).text()).append(/n/n);}String system你是一个严谨的问答助手。请只根据给定的参考资料回答问题资料中没有的信息明确回答资料中未提及不要编造。;String user参考资料/n context /n问题 question;returnollama.chat(system, user);}}3.7 REST 接口与配置package com.example.rag;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.HashMap;importjava.util.Map;RestController RequestMapping(/api/rag)public class RagController{Autowired private RagService rag;public static class AskRequest{public String document;public String question;}PostMapping(/ask)public MapString, Stringask(RequestBody AskRequest req){rag.index(req.document);String answerrag.ask(req.question);MapString, Stringresultnew HashMap();result.put(answer, answer);returnresult;}}package com.example.rag;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class RagApplication{public static void main(String[]args){SpringApplication.run(RagApplication.class, args);}}spring: application: name: rag-demo server: port:8080ollama: base-url: http://localhost:11434 embed-model: nomic-embed-text chat-model: qwen2.5:7b4.跑起来# 1. 装 OllamamacOS / Linux / Windows 都有安装包# 2. 拉两个模型一个算向量一个生成回答ollama pull nomic-embed-text ollama pull qwen2.5:7b# 3. 启动服务默认 11434 端口ollama serve# 4. 启动我们的 Spring Boot 应用mvn spring-boot:run# 5. 问一个问题curl-XPOST http://localhost:8080/api/rag/ask /-HContent-Type: application/json/-d{ document: Java 8 于 2014 年发布引入了 Lambda 表达式和 Stream API。Spring Boot 2.7 是 2.x 的最后一个版本。MySQL 是最流行的开源关系型数据库。Redis 是内存数据库常用来做缓存。, question: Spring Boot 2.7 是什么 }第一次跑会慢embedding 模型要加载7B 模型在 CPU 上生成一句话可能十几秒。正常现象耐心等。5.踩过的坑都是可复现的坑 1embedding 维度不一致直接崩溃真实发生我一开始用的是 nomic-embed-text1024 维后来想试试中文效果更好的 bge-m3换模型之后忘了清空内存里的旧向量。新旧向量维度不同cosine() 里 a.get(i) 直接 ArrayIndexOutOfBoundsException。这个坑暴露得很隐蔽代码没报错是检索时崩的而且只在新旧数据混在一起时出现。修复就是开头那段维度防御——换 embedding 模型 必须重建索引这条规则后来被我写进了项目 README。坑 2切分把语义拦腰截断早期 overlap0检索出来的片段经常从句子中间开始上下文不完整模型答得前言不搭后语。重叠窗口能缓解但治本的办法是按句子/段落边界切——我的下一版就改成按换行符和句号切。坑 3Ollama API 有两个版本的 embedding 接口老接口 /api/embeddings单条文本、返回字段叫 embedding新接口 /api/embed支持批量、字段叫 embeddings。网上教程新旧混杂照着老教程写字段名对不上返回 404 或解析失败。以官方文档为准别抄旧博客。坑 4中文检索效果差nomic-embed-text 以英文为主中文文档检索经常词不达意。换中文优化的模型如 bge-m3、qwen3-embedding立竿见影。这也是我上面说要换模型重建索引的原因之一。6.这个 demo 距离生产还差什么诚实地说这个 demo 只是把原理跑通了它本身不能上生产。差的东西我列全也是我下一步的路线图差距说明升级路径向量库内存存不了多少重启就丢pgvector复用 PostgreSQL/ Milvus / Qdrant索引管理每次问答都重建全量索引增量更新、文档级 upsert、删除切分质量固定窗口太粗暴按句子/段落/标题层级切分评估不知道检索准不准、答案对不对建带标准答案的评估集量化召回率与准确率框架手写链路维护成本高LangChain4jJava 生态原理和这里一模一样我的建议先手写一遍就像这篇再上框架。框架帮你省掉的是样板代码不是理解。出问题时理解原理的人能排查只会用框架的人只能等社区修。7.结论RAG 不神秘切分 → 向量化 → 检索 → 生成四步一百多行 Java 能跑通。传统行业也能玩Ollama 本地跑模型不需要 GPU不需要联网。手写一遍再上框架这是性价比最高的学习路径。如何学习大模型 AI 由于新岗位的生产效率要优于被取代岗位的生产效率所以实际上整个社会的生产效率是提升的。但是具体到个人只能说是“最先掌握AI的人将会比较晚掌握AI的人有竞争优势”。这句话放在计算机、互联网、移动互联网的开局时期都是一样的道理。我在一线科技企业深耕十二载见证过太多因技术卡位而跃迁的案例。那些率先拥抱 AI 的同事早已在效率与薪资上形成代际优势我意识到有很多经验和知识值得分享给大家也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。我们整理出这套AI 大模型突围资料包✅ 从零到一的 AI 学习路径图✅ 大模型调优实战手册附医疗/金融等大厂真实案例✅ 百度/阿里专家闭门录播课✅ 大模型当下最新行业报告✅ 真实大厂面试真题✅ 2026 最新岗位需求图谱所有资料 ⚡️ 朋友们如果有需要《AI大模型入门进阶学习资源包》下方扫码获取~① 全套AI大模型应用开发视频教程包含提示工程、RAG、LangChain、Agent、模型微调与部署、DeepSeek等技术点② 大模型系统化学习路线作为学习AI大模型技术的新手方向至关重要。 正确的学习路线可以为你节省时间少走弯路方向不对努力白费。这里我给大家准备了一份最科学最系统的学习成长路线图和学习规划带你从零基础入门到精通③ 大模型学习书籍文档学习AI大模型离不开书籍文档我精选了一系列大模型技术的书籍和学习文档电子版它们由领域内的顶尖专家撰写内容全面、深入、详尽为你学习大模型提供坚实的理论基础。④ AI大模型最新行业报告2025最新行业报告针对不同行业的现状、趋势、问题、机会等进行系统地调研和评估以了解哪些行业更适合引入大模型的技术和应用以及在哪些方面可以发挥大模型的优势。⑤ 大模型项目实战配套源码学以致用在项目实战中检验和巩固你所学到的知识同时为你找工作就业和职业发展打下坚实的基础。⑥ 大模型大厂面试真题面试不仅是技术的较量更需要充分的准备。在你已经掌握了大模型技术之后就需要开始准备面试我精心整理了一份大模型面试题库涵盖当前面试中可能遇到的各种技术问题让你在面试中游刃有余。以上资料如何领取为什么大家都在学大模型最近科技巨头英特尔宣布裁员2万人传统岗位不断缩减但AI相关技术岗疯狂扩招有3-5年经验大厂薪资就能给到50K*20薪不出1年“有AI项目经验”将成为投递简历的门槛。风口之下与其像“温水煮青蛙”一样坐等被行业淘汰不如先人一步掌握AI大模型原理应用技术项目实操经验“顺风”翻盘这些资料真的有用吗这份资料由我和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理现任上海殷泊信息科技CEO其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证服务航天科工、国家电网等1000企业以第一作者在IEEE Transactions发表论文50篇获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。资料内容涵盖了从入门到进阶的各类视频教程和实战项目无论你是小白还是有些技术基础的技术人员这份资料都绝对能帮助你提升薪资待遇转行大模型岗位。以上全套大模型资料如何领取

相关新闻

2026/9/2 16:40:55

LLM应用开发:主流可观测性与评估平台选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/2 16:35:55

游戏联动剧情设计解析:从世界观融合到叙事技术实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/2 16:35:55

在windows中,使用wsl和docker部署AnythingLLM

部署参考:Ollama【部署 06】使用Docker部署AnythingLLM让全员共享智能体(使用本地Ollama)-腾讯云开发者社区-腾讯云 但是部署的时候遇到一些问题,解决方法如下: 1. 使用命令拉取AnythingLLM时,出现 “err…

2026/9/2 16:55:58

Geek 卸载工具实测,系统自带卸载真的干净吗

为什么“卸载”后,电脑反而更慢了? 很多 Windows 用户都有一个共同的困惑:明明已经通过控制面板或设置菜单把软件卸载了,为什么 C 盘空间并没有释放多少?为什么系统运行久了还是会莫名卡顿,甚至偶尔弹出一些…

2026/9/2 16:55:58

ECharts 5.x地图下钻实战:GeoJSON获取与registerMap

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/2 16:55:58

海康威视NVR SDK开发实战:H.265预览回放与PyQt5对接经验

简介:海康威视H.265系列NVR SDK资料是一套面向安防软件开发者的二次开发工具包,适合具备C或C#基础、希望构建自定义监控平台或集成NVR能力的工程师。压缩包约20.03MB,按lib、doc、incCn、ClientDemo四个目录组织,集中提供预编译的…

2026/9/2 16:55:57

用uni-app与Vuex构建轻量刷题App:从题库设计到本地持久化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/2 16:50:57

AI 工程师 30 道高频题:从 Prompt 到 RAG 到 Agent

30 道题覆盖 80% AI 工程师面试考点——我面试 100 候选人后整理的。每题都附答案 代码,最后给一份按"提示词 / RAG / Agent"分层的复习清单。 题型分布模块题数难度占比Prompt 工程(Q1-Q8)8易-中27%Embedding / RAG(Q…

2026/9/1 16:02:17

vSound小提琴数字处理器实操指南:从接线到演出的完整配置

电小提琴或者原声小提琴插电演出,第一个绕不开的坎就是声音难听。原声琴的共鸣和空气感一旦进了拾音器,出来的往往是一坨干瘪、发尖、带着奇怪塑料味的信号。我当初第一次把琴接上乐队调音台,直接被主唱吐槽"你这声音像在锯钢丝"。…

2026/9/2 9:00:32

传感器接口IC如何攻克生物化学传感的微弱信号难题?

1. 从电极到比特流:为什么生物化学传感必须依赖专用接口IC 做生物化学传感的人都有过类似的经历:明明传感器本身性能很好,信号输出却一塌糊涂——噪声大、漂移明显、重复性差,怎么调都达不到预期。很多时候问题并不在传感器&#…

2026/9/2 8:41:06

STM32F411CEU6多通道ADC采集:扫描模式+DMA实现详解

1. 多通道 ADC 的用武之地把“Multichannel ADC”和“STM32F411CEU6”这两个关键字放在一起,其实就是嵌入式开发里最常遇到的一类需求:用一块不算贵的 MCU,同时采集多路模拟信号。STM32F411CEU6 是 48 引脚的 Cortex-M4F 主控,主频…

2026/9/2 0:03:41

单片机毕业设计-基于单片机与蓝牙通讯的输液状态监测终端设计与开发 基于 STM32 或 51 单片机的液位‑滴速‑温度多参数输液监护装置设计(024005)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/9/2 0:03:41

DeepSeek字幕翻译实战:从API调用到批量SRT转中文的完整方案

这次我们来看一个很实用的 DeepSeek 落地场景:用 DeepSeek 把英文视频字幕自动翻译成中文。具体案例是《恶魔君》1989 年第 28 集的英转中字幕任务,标题写得很直白,但背后其实是一整套可以复用的技术流程:字幕解析、模型调用、批量…

2026/9/2 0:03:41

用Python搭建搞笑语音助手:从语音识别到语音合成全教程

当你家里摆着一台天猫精灵,却总希望语音助手偶尔“不正经”一点,不用官方腔回答问题,而是张口就接几句搞笑段子,会是什么体验?我最近动手验证了一下这个想法——没有去改装任何市面上现有的智能音箱,而是直…

2026/9/2 1:15:22

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/2 1:15:22

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/2 1:15:20

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…