使用 PostgreSQL + pgvector 实现 RAG 向量存储与语义检索(Java 实战)

使用 PostgreSQL + pgvector 实现 RAG 向量存储与语义检索(Java 实战)

在 RAG(Retrieval-Augmented Generation)系统中,向量存储与相似度检索 是最核心的一环。

本文将使用 PostgreSQL + pgvector ,结合 Java + 阿里百炼 Embedding 模型,实现一个完整、可运行的向量存储与语义检索示例。

本文不仅关注"能跑",更重点解释 为什么这么设计 以及 RAG 中最容易踩的坑


一、pgvector 是什么?

pgvector 是 PostgreSQL 的一个扩展插件,为 PostgreSQL 提供了专门的 vector 类型,用于存储高维向量,并支持:

  • 余弦距离(cosine distance)
  • 欧氏距离(L2)
  • 内积(inner product)
  • 向量索引(ivfflat / hnsw)

这使得 PostgreSQL 可以直接作为 向量数据库 使用,非常适合中小规模 RAG 场景。


二、安装 PostgreSQL 与 pgvector

安装过程不再赘述。

👉 可参考 CSDN 博主 进击的女IT 的文章:
https://blog.csdn.net/weixin_63908159/article/details/156075242


三、建表与索引设计(非常重要)

1️⃣ 建表语句

sql 复制代码
CREATE TABLE document (
 id BIGSERIAL PRIMARY KEY,
 content TEXT NOT NULL,
 embedding vector(1024),
 create_time TIMESTAMP DEFAULT now()
);

⚠️ 注意
vector(1024) 必须与 Embedding 模型输出的向量维度一致。如果模型输出是 1536 维,这里必须改成 vector(1536),否则插入时会报错:expected xxx dimensions

2️⃣ 向量索引(否则数据一多会非常慢)

sql 复制代码
CREATE INDEX idx_document_embedding
ON document
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

查询前建议设置:

sql 复制代码
SET ivfflat.probes = 10;

四、Java 实体类设计

pgvector 在 Java 中无需特殊类型映射,直接使用 String 承载即可。

java 复制代码
@Data
public class Document {

    private Long id;

    private String content;

    /**
     * pgvector 字段
     * 使用 String 承载,例如:[0.12,0.34,...]
     */
    private String embedding;

    private LocalDateTime createTime;
}

五、Embedding 模型配置(阿里百炼)

配置文件 (application.yml):

yml 复制代码
alibaba:
  dashscope:
    key: sk-xxx
    url: https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings
    model: text-embedding-v4

配置类:

java 复制代码
@Configuration
@ConfigurationProperties(prefix = "alibaba.dashscope")
@Data
public class AlibabaDashscopeConfig {
    private String key;
    private String url;
    private String model;
}

六、文本向量化服务(EmbeddingService)

功能:文本 → 向量 (List<Float>)

java 复制代码
@Service
@Slf4j
public class EmbeddingService {

    @Autowired
    private AlibabaDashscopeConfig config;

    public List<Float> getEmbedding(String text) {
        if (StrUtil.isBlank(text)) {
            return List.of();
        }

        Map<String, Object> body = Map.of(
                "model", config.getModel(),
                "input", List.of(text)
        );

        HttpResponse response = HttpRequest.post(config.getUrl())
                .header("Authorization", "Bearer " + config.getKey())
                .header("Content-Type", "application/json")
                .body(JSONUtil.toJsonStr(body))
                .timeout(30000)
                .execute();

        JSONObject json = JSONUtil.parseObj(response.body());
        JSONArray embedding = json.getJSONArray("data")
                .getJSONObject(0)
                .getJSONArray("embedding");

        return embedding.toList(Float.class);
    }
}

七、向量存储与相似度查询

Mapper

java 复制代码
@Mapper
public interface DocumentMapper extends BaseMapper<Document> {

    /**
     * 向量相似度搜索(余弦距离)
     */
    @Select("""
        SELECT id, content
        FROM document
        ORDER BY embedding <=> #{embedding}::vector
        LIMIT #{limit}
    """)
    List<Document> searchByEmbedding(
            @Param("embedding") String embedding,
            @Param("limit") int limit
    );

    @Insert("""
        INSERT INTO document (content, embedding)
        VALUES (#{content}, #{embedding}::vector)
    """)
    void insertDocument(
            @Param("content") String content,
            @Param("embedding") String embedding
    );
}

Service

java 复制代码
@Service
@RequiredArgsConstructor
public class DocumentService {

    private final DocumentMapper mapper;
    private final EmbeddingService embeddingService;

    public void addDocument(String content) {
        List<Float> vector = embeddingService.getEmbedding(content);

        String pgVector = vector.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(",", "[", "]"));

        mapper.insertDocument(content, pgVector);
    }

    public List<Document> search(String query, int topK) {
        List<Float> vector = embeddingService.getEmbedding(query);
        if (vector.isEmpty()) {
            return List.of();
        }

        String pgVector = vector.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(",", "[", "]"));

        return mapper.searchByEmbedding(pgVector, topK);
    }
}

八、Controller 接口

java 复制代码
@RestController
@RequestMapping("/document")
@RequiredArgsConstructor
public class DocumentController {

    private final DocumentService service;

    @GetMapping("/add")
    public void add(@RequestParam String content) {
        service.addDocument(content);
    }

    @GetMapping("/search")
    public List<Document> search(
            @RequestParam String query,
            @RequestParam(defaultValue = "5") int topK
    ) {
        return service.search(query, topK);
    }
}

九、为什么"最近流行什么"查询不到结果?

这是语义检索中最容易被误解的一点:

向量检索 ≠ 关键词匹配

向量检索判断的是 语义是否在同一语义空间

示例:

文本 语义中心
Java 是一门流行的后端开发语言 编程 / 后端
最近流行什么 趋势 / 热点

👉 两者在语义空间中的距离很远,因此检索不到是正常且正确的行为。

正确做法:

  1. Query Rewrite(查询重写)
  2. 补充领域上下文

例如:

最近流行的后端开发语言有哪些?


十、总结

  • pgvector 可以让 PostgreSQL 直接作为向量数据库使用。
  • 向量检索本质是 语义相似度计算
  • RAG 的效果高度依赖于:
    • 文档内容的表达方式
    • Query 是否足够具体
    • 相似度阈值与 TopK 的设计

📌 项目完整源码地址(Gitee)
https://gitee.com/tfxing12138/rag-demo.git

相关推荐
heartbeat..4 小时前
Redis 中的锁:核心实现、类型与最佳实践
java·数据库·redis·缓存·并发
Prince-Peng4 小时前
技术架构系列 - 详解Redis
数据结构·数据库·redis·分布式·缓存·中间件·架构
虾说羊4 小时前
redis中的哨兵机制
数据库·redis·缓存
_F_y4 小时前
MySQL视图
数据库·mysql
2301_790300964 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python
4 小时前
java关于内部类
java·开发语言
好好沉淀4 小时前
Java 项目中的 .idea 与 target 文件夹
java·开发语言·intellij-idea
gusijin4 小时前
解决idea启动报错java: OutOfMemoryError: insufficient memory
java·ide·intellij-idea
To Be Clean Coder4 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
吨~吨~吨~4 小时前
解决 IntelliJ IDEA 运行时“命令行过长”问题:使用 JAR
java·ide·intellij-idea