使用 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

相关推荐
醉颜凉16 小时前
PostgreSQL 模式(SCHEMA)详解:数据库对象的命名空间管理
数据库·postgresql
AI题库16 小时前
PostgreSQL 18 默认密码修改全指南:从安装到安全加固
数据库·安全·postgresql
七夜zippoe16 小时前
告别SQL恐惧症:我用飞算JavaAI的SQL Chat,把数据库变成了“聊天室”
java·数据库·sql·ai·javaai
半桔16 小时前
【MySQL数据库】SQL 查询封神之路:步步拆解核心操作,手把手帮你解锁高阶玩法
linux·数据库·sql·mysql·adb·oracle
猫头虎16 小时前
[精选] 2025最新MySQL和PostgreSQL区别、迁移、安全、适用场景全解析
运维·数据库·mysql·安全·postgresql·云原生·容器
No8g攻城狮16 小时前
【SQL】MySQL中空值处理COALESCE函数
数据库·sql·mysql·postgresql·sqlserver
心本无晴.16 小时前
RAG检索优化:文本分块策略如何大幅提升检索准确度
java·linux·服务器
西门吹雪分身16 小时前
K8S之Ingress
java·容器·kubernetes·k8s
无名之逆16 小时前
你可能不需要WebSocket-服务器发送事件的简单力量
java·开发语言·前端·后端·计算机·rust·编程
Remember_99316 小时前
一文吃透Java WebSocket:原理、实现与核心特性解析
java·开发语言·网络·websocket·网络协议·http·p2p