基于 LangChain4j + Spring Boot3 + Milvus 实现大数据量向量数据库案例

你想要在之前的整合案例基础上,升级为大数据量场景下的向量数据库方案(替代原有的模糊查询),我会以主流的开源向量数据库 Milvus 为例,带你实现从环境搭建到完整 RAG 流程的全链路案例,解决大数据量下语义检索精度低、性能差的问题。

一、核心前置知识

1. 为什么需要向量数据库?

  • 模糊查询(LIKE %关键词%):仅匹配字符,无法理解语义,大数据量下(百万级+)查询慢、精度低;
  • 向量数据库(Milvus):将文本转为向量(Embedding),通过余弦相似度/欧氏距离检索语义相似的内容,毫秒级响应,精度远超字符匹配,适配大数据量场景。

2. 技术栈选型

组件 作用
Milvus 开源向量数据库(支持百亿级向量检索,轻量版 Milvus Standalone 适合入门)
LangChain4j 封装 Embedding 生成、向量入库/检索逻辑
豆包 Embedding API 将文本转为向量(替代本地模型,降低部署成本)
Spring Boot3 基础框架 + 整合各组件
MyBatis-Plus 存储原始文本数据(向量数据库仅存向量和关联 ID,原始数据仍存在关系库)

3. 环境准备

  • 已完成上一版 LangChain4j + Spring Boot3 + MyBatis-Plus 基础环境;
  • 安装 Milvus(推荐 Docker 部署,最快上手):
    # 1. 下载 Milvus 单机版配置文件
    wget https://github.com/milvus-io/milvus/releases/download/v2.4.3/milvus-standalone-docker-compose.yml -O docker-compose.yml
    
    # 2. 启动 Milvus(需提前安装 Docker & Docker Compose)
    docker-compose up -d
    
    # 3. 验证启动成功(查看容器状态)
    docker-compose ps
    
  • 豆包 Embedding API 权限(需确保 API Key 可调用 /embeddings 接口,同 LLM API Key 通用)。

二、阶段1:集成 Milvus 依赖与配置

1.1 新增 Maven 依赖(pom.xml)

在原有依赖基础上添加:

<!-- Milvus Java SDK -->
<dependency>
    <groupId>io.milvus</groupId>
    <artifactId>milvus-sdk-java</artifactId>
    <version>2.4.3</version>
</dependency>

<!-- LangChain4j 整合 Milvus(简化向量操作) -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-milvus</artifactId>
    <version>0.32.0</version>
</dependency>

<!-- JSON 工具(可选,用于调试) -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.42</version>
</dependency>

1.2 新增配置(application.yml)

# 原有配置不变,新增以下内容
milvus:
  host: 127.0.0.1    # Milvus 服务地址(Docker 部署默认本机)
  port: 19530        # Milvus 默认端口
  database-name: default
  collection-name: knowledge_vector # 向量集合名称(相当于表)
  dimension: 1536    # 向量维度(豆包 Embedding 输出维度为 1536)
  index-type: IVF_FLAT # 索引类型(入门选 IVF_FLAT,平衡精度和性能)
  metric-type: COSINE # 相似度计算方式(余弦相似度)

doubao:
  # 原有 LLM 配置不变,新增 Embedding 配置
  embedding:
    api-key: ${doubao.llm.api-key} # 复用 API Key
    base-url: ${doubao.llm.base-url} # 复用 API 地址
    model: doubao-embedding # 豆包 Embedding 模型名称

三、阶段2:核心代码实现

3.1 配置类(向量数据库 + Embedding)

package com.example.config;

import dev.langchain4j.data.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
import io.milvus.param.ConnectParam;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

/**
 * 向量数据库 & Embedding 配置类
 */
@Configuration
public class VectorConfig {

    @Resource
    private DouBaoProperties douBaoProperties;

    @Resource
    private MilvusProperties milvusProperties;

    /**
     * 初始化豆包 Embedding 模型(文本转向量)
     */
    @Bean
    public EmbeddingModel doubaoEmbeddingModel() {
        return OpenAiEmbeddingModel.builder()
                .apiKey(douBaoProperties.getEmbedding().getApiKey())
                .baseUrl(douBaoProperties.getEmbedding().getBaseUrl() + "/embeddings") // 豆包 Embedding 接口路径
                .modelName(douBaoProperties.getEmbedding().getModel())
                .build();
    }

    /**
     * 初始化 Milvus 向量存储
     */
    @Bean
    public MilvusEmbeddingStore milvusEmbeddingStore() {
        // 1. 构建 Milvus 连接参数
        ConnectParam connectParam = ConnectParam.newBuilder()
                .withHost(milvusProperties.getHost())
                .withPort(milvusProperties.getPort())
                .withDatabaseName(milvusProperties.getDatabaseName())
                .build();

        // 2. 构建向量存储(LangChain4j 封装,简化操作)
        return MilvusEmbeddingStore.builder()
                .connectParam(connectParam)
                .collectionName(milvusProperties.getCollectionName())
                .dimension(milvusProperties.getDimension()) // 向量维度必须和 Embedding 输出一致
                .indexType(milvusProperties.getIndexType())
                .metricType(milvusProperties.getMetricType())
                .build();
    }

    /**
     * Milvus 配置属性类(绑定 yml 配置)
     */
    @Data
    @Component
    @ConfigurationProperties(prefix = "milvus")
    public static class MilvusProperties {
        private String host;
        private Integer port;
        private String databaseName;
        private String collectionName;
        private Integer dimension;
        private String indexType;
        private String metricType;
    }
}

注意:需在 DouBaoProperties 中新增 Embedding 内部类:

@Data
public static class EmbeddingProperties {
    private String apiKey;
    private String baseUrl;
    private String model;
}

3.2 向量入库工具类(初始化知识库)

package com.example.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.entity.KnowledgeBase;
import com.example.mapper.KnowledgeBaseMapper;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * 向量入库工具:项目启动时将知识库文本转为向量存入 Milvus
 * (实际生产可改为定时任务/接口触发,避免每次启动重复入库)
 */
@Slf4j
@Component
public class VectorIngestor implements CommandLineRunner {

    @Resource
    private KnowledgeBaseMapper knowledgeBaseMapper;

    @Resource
    private EmbeddingModel doubaoEmbeddingModel;

    @Resource
    private EmbeddingStore<TextSegment> milvusEmbeddingStore;

    @Override
    public void run(String... args) throws Exception {
        // 1. 从 MySQL 读取所有知识库数据
        List<KnowledgeBase> knowledgeList = knowledgeBaseMapper.selectList(new LambdaQueryWrapper<>());
        if (knowledgeList.isEmpty()) {
            log.warn("知识库无数据,跳过向量入库");
            return;
        }

        // 2. 初始化 LangChain4j 向量入库器(简化分片、编码、入库流程)
        EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
                .embeddingModel(doubaoEmbeddingModel) // 文本转向量模型
                .embeddingStore(milvusEmbeddingStore) // 向量存储(Milvus)
                .build();

        // 3. 逐行入库
        for (KnowledgeBase knowledge : knowledgeList) {
            // 构建文档(包含文本和元数据,元数据可用于过滤)
            Document document = Document.from(knowledge.getContent(),
                    Metadata.from("id", knowledge.getId().toString(), "title", knowledge.getTitle()));
            // 入库(自动分片→转向量→存入 Milvus)
            ingestor.ingest(document);
            log.info("知识库ID:{} 向量入库成功", knowledge.getId());
        }
        log.info("所有知识库向量入库完成,共{}条数据", knowledgeList.size());
    }
}

3.3 语义检索服务(核心:替代模糊查询)

package com.example.service;

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingStore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 向量检索服务:大数据量下的语义检索
 */
@Slf4j
@Service
public class VectorSearchService {

    @Resource
    private EmbeddingModel doubaoEmbeddingModel;

    @Resource
    private EmbeddingStore<TextSegment> milvusEmbeddingStore;

    /**
     * 语义检索:根据问题检索相似知识
     * @param question 用户问题
     * @param topK 返回最相似的K条结果(建议5-10)
     * @return 相似知识文本列表
     */
    public List<String> semanticSearch(String question, int topK) {
        // 步骤1:将用户问题转为向量
        Embedding questionEmbedding = doubaoEmbeddingModel.embed(question).content();

        // 步骤2:Milvus 检索相似向量(语义相似)
        List<EmbeddingMatch<TextSegment>> matches = milvusEmbeddingStore.findRelevant(questionEmbedding, topK);

        // 步骤3:提取匹配到的知识文本(过滤低相似度结果,如相似度<0.7)
        double similarityThreshold = 0.7; // 相似度阈值,可根据业务调整
        return matches.stream()
                .filter(match -> match.score() >= similarityThreshold) // 过滤低相似度
                .map(match -> match.embedded().text()) // 提取文本内容
                .collect(Collectors.toList());
    }
}

3.4 升级 RAG 服务(替换模糊查询为向量检索)

package com.example.service;

import cn.hutool.core.collection.CollectionUtil;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 升级后的 RAG 服务:基于向量数据库的语义检索
 */
@Service
public class AdvancedRagService {

    @Resource
    private ChatLanguageModel doubaoChatModel;

    @Resource
    private VectorSearchService vectorSearchService;

    @Resource
    private DouBaoProperties douBaoProperties;

    /**
     * 大数据量下的 RAG 问答
     */
    public String ragChat(String question) {
        // 步骤1:向量数据库语义检索(替代原模糊查询)
        List<String> relatedKnowledge = vectorSearchService.semanticSearch(question, 5); // 取最相似的5条

        // 步骤2:构建 Prompt(复用原逻辑,无数据时降级为直接问答)
        String promptTemplateStr = douBaoProperties.getPrompt().getRagTemplate();
        String knowledgeStr = CollectionUtil.isEmpty(relatedKnowledge)
                ? "无相关参考知识,请直接回答问题"
                : String.join("\n", relatedKnowledge);

        // 步骤3:填充 Prompt 参数并调用 LLM
        PromptTemplate promptTemplate = PromptTemplate.from(promptTemplateStr);
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("related_knowledge", knowledgeStr);
        parameters.put("question", question);
        Prompt prompt = promptTemplate.apply(parameters);

        return doubaoChatModel.generate(prompt.text());
    }
}

3.5 新增控制器(测试向量 RAG)

package com.example.controller;

import com.example.service.AdvancedRagService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * 高级 RAG 控制器(基于向量数据库)
 */
@RestController
@RequestMapping("/api/advanced-rag")
public class AdvancedRagController {

    @Resource
    private AdvancedRagService advancedRagService;

    /**
     * 大数据量语义检索 RAG 问答
     */
    @GetMapping("/chat")
    public String ragChat(@RequestParam String question) {
        return advancedRagService.ragChat(question);
    }
}

四、测试验证

4.1 数据准备

  1. 向 MySQL knowledge_base 表插入大量测试数据(如1000+条不同的技术知识);
  2. 启动项目,VectorIngestor 会自动将所有数据转为向量存入 Milvus;
  3. 验证 Milvus 数据:
    # 进入 Milvus 容器
    docker exec -it milvus-standalone milvus_cli
    
    # 连接 Milvus
    connect -h 127.0.0.1 -p 19530
    
    # 查看向量集合
    list collections
    
    # 查看集合数据量
    describe collection -c knowledge_vector
    

4.2 接口测试

访问升级后的 RAG 接口:

http://localhost:8080/api/advanced-rag/chat?question=Spring Boot3要求的JDK版本是多少

预期结果

  • 即使问题表述为“Spring Boot3需要什么版本的JDK”(与数据库中“Spring Boot3基于Spring Framework 6,要求JDK17+”语义相似但字符不同),也能精准检索到相关知识;
  • 大数据量下(10万+条),响应时间仍在100-500ms(远快于模糊查询)。

五、大数据量优化(精通级)

5.1 性能优化

  1. 索引优化
    • 入门用 IVF_FLAT,百万级数据可改为 HNSW(更快的检索速度);
    • 调整 nlist 参数(IVF_FLAT 索引):nlist = 数据量^(1/2),平衡精度和性能。
  2. 批量入库
    替换逐行入库为批量入库,提升初始化效率:
    // 批量构建文档
    List<Document> documents = knowledgeList.stream()
            .map(knowledge -> Document.from(knowledge.getContent(),
                    Metadata.from("id", knowledge.getId().toString(), "title", knowledge.getTitle())))
            .collect(Collectors.toList());
    // 批量入库
    ingestor.ingest(documents);
    
  3. 缓存 Embedding 结果
    对高频文本(如固定知识库)缓存向量结果,避免重复调用 Embedding API。

5.2 数据管理

  1. 增量更新
    新增/修改知识库数据时,仅更新对应向量(而非全量重建):
    // 增量入库单条数据
    public void addKnowledgeToVector(KnowledgeBase knowledge) {
        Document document = Document.from(knowledge.getContent(),
                Metadata.from("id", knowledge.getId().toString(), "title", knowledge.getTitle()));
        ingestor.ingest(document);
    }
    
  2. 向量删除
    根据元数据(如 ID)删除向量:
    public void deleteKnowledgeFromVector(Long knowledgeId) {
        milvusEmbeddingStore.deleteByMetadata("id", knowledgeId.toString());
    }
    

5.3 分布式部署(超大数据量)

  • Milvus 从 Standalone 升级为 Cluster 模式(支持分片、副本);
  • 引入 Redis 做检索结果缓存,进一步降低 Milvus 压力;
  • 使用豆包私有化部署的 Embedding 模型,降低网络延迟。

六、总结

核心要点回顾

  1. 核心流程:文本 → Embedding 模型转向量 → 存入 Milvus → 用户问题转向量 → Milvus 语义检索 → 结合 LLM 生成回答;
  2. 关键配置:向量维度(豆包 Embedding 为 1536)、相似度算法(余弦相似度)、索引类型(入门选 IVF_FLAT)需匹配;
  3. 优化方向:大数据量下重点做索引调优、批量入库、增量更新,超大规模需升级 Milvus 集群。

核心优势

  • 精度:语义检索替代字符匹配,即使问题表述不同也能精准找到相关知识;
  • 性能:Milvus 对向量检索做了极致优化,百万级数据毫秒级响应;
  • 扩展性:支持水平扩容,可适配从万级到亿级的向量数据场景。

这个案例完整覆盖了大数据量下向量数据库的集成、使用和优化,你可以基于此扩展到生产环境(如增加权限控制、限流、监控等)。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐