Spring AI 学习篇(六)| 向量数据库的选择与Spring AI集成

Spring AI 学习篇(六)| 向量数据库的选择与Spring AI集成

  • 一、本章核心学习目标
  • 二、前置知识准备
  • 三、为什么我们需要向量数据库?
  • 四、2026年主流向量数据库对比与选型
    • [1. 向量数据库核心选型指标](#1. 向量数据库核心选型指标)
    • [2. 主流向量数据库横向对比](#2. 主流向量数据库横向对比)
    • [3. 向量数据库选型决策树](#3. 向量数据库选型决策树)
  • [五、Spring AI向量数据库统一API详解](#五、Spring AI向量数据库统一API详解)
    • [1. 本地开发首选:Chroma集成](#1. 本地开发首选:Chroma集成)
      • [(1) 添加Maven依赖](#(1) 添加Maven依赖)
      • [(2) 基础配置(application.yml)](#(2) 基础配置(application.yml))
      • [(3) 启动本地Chroma服务](#(3) 启动本地Chroma服务)
    • [2. VectorStore核心API](#2. VectorStore核心API)
    • [3. 完整代码示例](#3. 完整代码示例)
      • [(1) 注入VectorStore](#(1) 注入VectorStore)
      • [(2) 添加文档到向量数据库](#(2) 添加文档到向量数据库)
      • [(3) 相似度检索](#(3) 相似度检索)
      • [(4) 删除文档](#(4) 删除文档)
    • [4. 切换到其他向量数据库](#4. 切换到其他向量数据库)
      • [(1) 修改依赖](#(1) 修改依赖)
      • [(2) 修改配置](#(2) 修改配置)
  • 六、企业级最佳实践
    • [1. 合理设置Top K值](#1. 合理设置Top K值)
    • [2. 善用元数据过滤](#2. 善用元数据过滤)
    • [3. 批量导入数据](#3. 批量导入数据)
    • [4. 定期备份数据](#4. 定期备份数据)
    • [5. 选择合适的索引类型](#5. 选择合适的索引类型)
  • 七、常见坑与解决方案
    • [1. ❌ 嵌入模型与向量数据库维度不匹配](#1. ❌ 嵌入模型与向量数据库维度不匹配)
    • [2. ❌ 检索结果不准确](#2. ❌ 检索结果不准确)
    • [3. ❌ 批量导入内存溢出](#3. ❌ 批量导入内存溢出)
    • [4. ❌ Chroma嵌入式模式性能差](#4. ❌ Chroma嵌入式模式性能差)
    • [5. ❌ 忘记添加元数据](#5. ❌ 忘记添加元数据)
  • 八、本章总结与下章预告
  • 九、课后练习

一、本章核心学习目标

学完本章,你将能够:

  1. 深刻理解向量数据库的本质和解决的核心问题
  2. 独立完成主流向量数据库的对比与选型
  3. 熟练使用Spring AI向量数据库统一API
  4. 快速搭建本地Chroma向量数据库并完成CRUD操作
  5. 实现向量相似度检索与批量数据导入
  6. 掌握向量数据库的企业级最佳实践
  7. 避开向量数据库使用中的常见坑

二、前置知识准备

  • 已经完成第5篇的学习,理解嵌入模型与向量表示的本质
  • 熟练使用Spring AI EmbeddingClient生成向量
  • 了解关系型数据库的基本原理和使用方法
  • 熟悉Spring Boot的依赖注入和配置管理

三、为什么我们需要向量数据库?

在第5篇我们已经学会了如何将文本转换为向量,但新的问题来了:如何高效地存储和检索这些向量?

很多人会问:"我能不能用MySQL存向量?"答案是:可以,但性能极差

传统关系型数据库的局限性

假设你有100万条向量数据,每条向量是1024维。当用户发起一个查询时,你需要:

  1. 将用户的问题转换为向量
  2. 遍历数据库中所有100万条向量
  3. 计算每条向量与查询向量的余弦相似度
  4. 排序后返回最相似的Top 10条结果

这个过程需要进行100万次向量计算,在MySQL中可能需要几十秒甚至几分钟才能完成,完全无法满足实时性要求。

向量数据库的核心优势

向量数据库是专门为存储和检索向量设计的数据库,它通过**近似最近邻搜索(ANN)**算法,将检索时间从O(n)降低到O(log n),可以在毫秒级从百万级甚至亿级数据中找到最相似的结果。

核心能力对比

数据库类型 存储能力 检索速度 相似度计算 适用场景
MySQL 支持 秒级/分钟级 精确计算 小数据量(<1万条)
向量数据库 支持 毫秒级 近似计算 大数据量(>1万条)

预告式提及:向量数据库是RAG系统的核心存储组件,下一章我们将把嵌入模型和向量数据库结合起来,实现一个完整的基础RAG系统。

四、2026年主流向量数据库对比与选型

1. 向量数据库核心选型指标

指标 含义 影响
部署难度 安装和配置的复杂程度 影响开发和运维成本
性能 单节点每秒查询次数(QPS) 影响系统的并发能力
扩展性 是否支持分布式集群 影响系统的容量上限
Spring AI支持 是否有官方原生支持 影响开发效率
开源协议 是否可以免费商用 关系到企业的法律风险

2. 主流向量数据库横向对比

数据库名称 类型 部署难度 单节点QPS 分布式支持 Spring AI支持 开源协议 适用场景
Chroma 轻量本地 极低(无需单独部署) 1000+ ❌ 不支持 ✅ 官方原生 Apache 2.0 本地开发、原型验证、小团队内部使用(<100万条)
Qdrant 生产级 中等 5000+ ✅ 支持 ✅ 官方原生 Apache 2.0 中小规模生产环境(100万-1亿条)
Milvus 生产级 较高 10000+ ✅ 支持 ✅ 官方原生 Apache 2.0 大规模生产环境(>1亿条)
Redis Stack 缓存型 极低 10000+ ✅ 支持 ✅ 官方原生 RSAL(源码可用) 已有Redis基础设施、高并发场景
Pinecone 云服务 极低 10000+ ✅ 支持 ✅ 官方原生 商业 不想自己运维的云原生场景

3. 向量数据库选型决策树

复制代码
向量数据库选型决策树:
├── 本地开发/原型验证 → Chroma(首选,无需部署,开箱即用)
├── 生产环境
│   ├── 已有Redis基础设施 → Redis Stack
│   ├── 数据量<1亿条 → Qdrant(部署简单,性能优秀)
│   ├── 数据量>1亿条 → Milvus(扩展性最好)
│   └── 不想自己运维 → Pinecone(云服务)

2026年最新进展:Chroma已经发布了1.0稳定版,性能和稳定性大幅提升,完全可以满足小团队和中小规模生产环境的需求。Spring AI对所有主流向量数据库都提供了官方原生支持,切换数据库只需要修改依赖和配置,业务代码完全不需要改变。

五、Spring AI向量数据库统一API详解

ChatClientEmbeddingClient一样,Spring AI为所有向量数据库提供了统一的VectorStore接口。无论你使用的是Chroma、Qdrant还是Milvus,代码完全相同。

1. 本地开发首选:Chroma集成

Chroma是本地开发和原型验证的最佳选择,它不需要单独部署,会自动在本地创建文件存储。

(1) 添加Maven依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-chroma-store</artifactId>
</dependency>

(2) 基础配置(application.yml)

yaml 复制代码
spring:
  ai:
    # 嵌入模型配置(我们使用本地Ollama BGE-M4)
    ollama:
      base-url: http://localhost:11434
      embedding:
        model: bge-m4
    # Chroma向量数据库配置
    vectorstore:
      chroma:
        host: http://localhost:8000
        collection-name: spring-ai-demo

(3) 启动本地Chroma服务

虽然Chroma可以嵌入式运行,但推荐使用Docker启动独立服务,性能更好且数据更安全:

cmd 复制代码
docker run -d -p 8000:8000 -v D:\chroma-data:/chroma/chroma chromadb/chroma:1.0.0

2. VectorStore核心API

Spring AI的VectorStore接口提供了以下核心操作(方法签名以 Spring AI 1.0 实际 API 为准):

java 复制代码
// 添加文档
void add(List<Document> documents);

// 删除文档
void delete(String idList);  // 或 delete(List<String> idList)

// 相似度检索
List<Document> similaritySearch(SearchRequest request);

// 检索结果自带分数,通过 doc.getScore() 获取

注意:不同版本 Spring AI 的方法签名可能有细微差异,以 IDE 自动补全和官方文档为准。

3. 完整代码示例

(1) 注入VectorStore

java 复制代码
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/vector")
public class VectorStoreController {

    private final VectorStore vectorStore;

    // 构造函数注入VectorStore,Spring AI会自动为我们创建实例
    public VectorStoreController(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

(2) 添加文档到向量数据库

java 复制代码
    // 添加单个文档
    @PostMapping("/add")
    public String addDocument(@RequestParam String content) {
        Document document = new Document(content);
        vectorStore.add(List.of(document));
        return "文档添加成功,ID:" + document.getId();
    }

    // 批量添加文档
    @PostMapping("/add/batch")
    public List<String> addDocuments(@RequestBody List<String> contents) {
        List<Document> documents = contents.stream()
                .map(Document::new)
                .toList();
        vectorStore.add(documents);
        return documents.stream()
                .map(Document::getId)
                .toList();
    }

    // 添加带元数据的文档
    @PostMapping("/add-with-metadata")
    public String addDocumentWithMetadata(@RequestParam String content,
                                          @RequestParam String author,
                                          @RequestParam String category) {
        Map<String, Object> metadata = Map.of(
                "author", author,
                "category", category,
                "createTime", System.currentTimeMillis()
        );
        Document document = new Document(content, metadata);
        vectorStore.add(List.of(document));
        return "文档添加成功,ID:" + document.getId();
    }

(3) 相似度检索

java 复制代码
    // 基础相似度检索,返回Top 5个最相似的文档
    @GetMapping("/search")
    public List<Document> search(@RequestParam String query) {
        return vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(query)
                        .topK(5)
                        .build()
        );
    }

    // 带分数的相似度检索
    @GetMapping("/search-with-scores")
    public List<Map<String, Object>> searchWithScores(@RequestParam String query) {
        return vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(query)
                        .topK(5)
                        .build()
        ).stream()
                .map(doc -> Map.of(
                        "content", doc.getContent(),
                        "score", doc.getScore(),
                        "metadata", doc.getMetadata()
                ))
                .toList();
    }

    // 带过滤条件的相似度检索
    @GetMapping("/search-with-filter")
    public List<Document> searchWithFilter(@RequestParam String query,
                                           @RequestParam String category) {
        return vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(query)
                        .topK(5)
                        .filterExpression("category == '" + category + "'")
                        .build()
        );
    }

(4) 删除文档

java 复制代码
    // 根据ID删除文档
    @DeleteMapping("/delete")
    public String deleteDocument(@RequestParam String id) {
        vectorStore.delete(List.of(id));
        return "文档删除成功";
    }

4. 切换到其他向量数据库

这是Spring AI最强大的功能之一。切换到Qdrant只需要两步:

(1) 修改依赖

xml 复制代码
<!-- 注释掉Chroma依赖 -->
<!-- <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-chroma-store</artifactId>
</dependency> -->

<!-- 添加Qdrant依赖 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
</dependency>

(2) 修改配置

yaml 复制代码
spring:
  ai:
    # 注释掉Chroma配置
    # vectorstore:
    #   chroma:
    #     host: http://localhost:8000
    #     collection-name: spring-ai-demo
    
    # 添加Qdrant配置
    vectorstore:
      qdrant:
        host: http://localhost:6333
        collection-name: spring-ai-demo

不需要修改任何一行业务代码,所有之前写的接口都可以正常使用。

六、企业级最佳实践

1. 合理设置Top K值

  • 一般设置为3-10个结果
  • 太少会遗漏相关信息,太多会引入无关信息
  • 可以根据实际场景动态调整

2. 善用元数据过滤

在文档中添加元数据(如作者、分类、创建时间等),检索时可以通过元数据过滤结果,大幅提升检索准确率。

3. 批量导入数据

不要每次只添加一个文档,尽量批量导入,这样可以大幅提升效率。

java 复制代码
// 推荐:批量导入
List<Document> documents = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    documents.add(new Document("文档" + i));
}
vectorStore.add(documents);

// 不推荐:逐个添加
for (int i = 0; i < 1000; i++) {
    vectorStore.add(List.of(new Document("文档" + i)));
}

4. 定期备份数据

向量数据库中的数据是宝贵的资产,一定要定期备份。Chroma可以直接备份数据目录,Milvus和Qdrant提供了官方的备份工具。

5. 选择合适的索引类型

不同的向量数据库支持不同的索引类型,选择合适的索引类型可以平衡检索速度和准确率:

  • HNSW:速度快,准确率高,内存占用大(推荐)
  • IVF:速度快,准确率稍低,内存占用小
  • FLAT:准确率最高,速度最慢,适合小数据量

七、常见坑与解决方案

1. ❌ 嵌入模型与向量数据库维度不匹配

问题 :嵌入模型生成的向量维度和向量数据库集合的维度不一致,导致添加文档失败

解决方案:创建集合时明确指定维度,确保与嵌入模型的维度一致。BGE-M4是1024维,DeepSeek是1536维。

2. ❌ 检索结果不准确

问题 :相似度检索返回的结果与查询不相关

解决方案

  • 检查嵌入模型是否选择正确(中文场景优先使用BGE-M4)
  • 调整Top K值
  • 添加元数据过滤
  • 优化文本切分策略(我们会在第7篇详细讲解)

3. ❌ 批量导入内存溢出

问题 :一次性导入大量文档导致内存溢出

解决方案:分批次导入,每次导入100-1000个文档。

4. ❌ Chroma嵌入式模式性能差

问题 :使用Chroma嵌入式模式,数据量大时性能很差

解决方案:使用Docker启动独立的Chroma服务,性能会显著提升。

5. ❌ 忘记添加元数据

问题 :添加文档时没有添加元数据,后续无法进行过滤和管理

解决方案:养成添加元数据的习惯,至少包含创建时间、来源和分类。

八、本章总结与下章预告

本章总结

  1. 向量数据库是专门为存储和检索向量设计的数据库,通过ANN算法实现毫秒级检索
  2. 本地开发首选Chroma,生产环境根据数据量选择Qdrant或Milvus
  3. Spring AI提供了统一的VectorStore接口,切换数据库只需要修改依赖和配置
  4. VectorStore支持添加、删除、相似度检索和元数据过滤等核心操作
  5. 合理设置Top K值、善用元数据过滤和批量导入可以大幅提升系统性能

预告式提及:我们现在已经掌握了嵌入模型和向量数据库这两个RAG的核心组件。下一章我们将把它们结合起来,从零实现一个完整的基础RAG系统,解决大模型的知识截止和幻觉问题。

下章预告

下一章我们将学习从零实现一个基础RAG系统。你将学会:

  • RAG的完整工作流程与原理详解
  • 文档解析:支持PDF、Word、Markdown等多种格式
  • 文本切分策略:固定长度切分 vs 语义切分
  • 向量索引构建与检索流程
  • 基础RAG问答接口的完整实现

九、课后练习

  1. 使用Docker启动本地Chroma服务,配置Spring AI集成
  2. 添加10篇关于Java和Spring AI的文档到向量数据库
  3. 实现一个相似度检索接口,测试不同查询的返回结果
  4. 为文档添加元数据(分类:Java、Spring AI),实现带分类过滤的检索
  5. 尝试将向量数据库切换为Qdrant,观察业务代码是否需要修改