29 · 在 Java / MyBatis 中操作 pgvector

29 · 在 Java / MyBatis 中操作 pgvector

一句话:不借助 Spring AI,直接用 JDBC / MyBatis 把向量当字符串 '[1,2,3]' 读写即可,理解这层底层,再用框架就不迷糊。

本章刻意"手动挡",让你看清框架底下到底发生了什么。项目技术栈是 Java/Spring/MyBatis,正好对口。

1. 核心认知:向量在 JDBC 里就是字符串

pgvector 的 vector 类型,JDBC 默认没有专门的 Java 类型映射。最通用的做法:

  • 写入:把 float[] 拼成 "[0.1,0.2,0.3]" 字符串,以文本发给 PG,PG 自动转成 vector
  • 读取:PG 返回 "[0.1,0.2,0.3]" 字符串,Java 再解析回 float[]

2. 一个工具方法:float[] ↔ 字符串

public final class VectorUtils {

    // float[] -> "[0.1,0.2,0.3]"
    public static String toVectorString(float[] v) {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < v.length; i++) {
            if (i > 0) sb.append(',');
            sb.append(v[i]);
        }
        return sb.append(']').toString();
    }

    // "[0.1,0.2,0.3]" -> float[]
    public static float[] parse(String s) {
        String body = s.substring(1, s.length() - 1); // 去掉 [ ]
        if (body.isEmpty()) return new float[0];
        String[] parts = body.split(",");
        float[] v = new float[parts.length];
        for (int i = 0; i < parts.length; i++) {
            v[i] = Float.parseFloat(parts[i].trim());
        }
        return v;
    }
}

3. 纯 JDBC 写入

关键:把向量字符串用 ::vector 显式转型,或直接作为文本传给 vector 列。

String sql = "INSERT INTO documents (content, embedding) VALUES (?, ?::vector)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, "轻薄商务笔记本");
    ps.setString(2, VectorUtils.toVectorString(embedding)); // embedding 是 float[]
    ps.executeUpdate();
}

?::vector 告诉 PG 把这个字符串参数当 vector 处理。

4. 纯 JDBC 相似度检索

查询向量同样以字符串 + ::vector 传入:

String sql =
    "SELECT content, embedding <=> ?::vector AS distance " +
    "FROM documents ORDER BY distance LIMIT ?";

try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, VectorUtils.toVectorString(queryVec));
    ps.setInt(2, 5);
    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
            System.out.println(rs.getString("content") + " : " + rs.getDouble("distance"));
        }
    }
}

5. MyBatis 写法

5.1 实体类

向量字段用 Stringfloat[] 都行。用 String 最省事:

public class Document {
    private Long id;
    private String content;
    private String embedding;   // 存 "[0.1,0.2,...]"
    // getter/setter...
}

5.2 Mapper XML —— 插入

<insert id="insertDoc" parameterType="Document">
    INSERT INTO documents (content, embedding)
    VALUES (#{content}, #{embedding}::vector)
</insert>

注意 #{embedding}::vector,让 PG 把字符串转成向量。

5.3 Mapper XML —— 相似度检索

查询向量作为字符串参数传入:

<select id="searchByVector" resultType="Document">
    SELECT id, content,
           embedding <![CDATA[ <=> ]]> #{queryVec}::vector AS distance
    FROM documents
    ORDER BY embedding <![CDATA[ <=> ]]> #{queryVec}::vector
    LIMIT #{topK}
</select>

<=> 里有 <,在 XML 里用 <![CDATA[ ... ]]> 包起来避免被当成标签。

对应 Mapper 接口:

List<Document> searchByVector(@Param("queryVec") String queryVec,
                              @Param("topK") int topK);

调用:

String q = VectorUtils.toVectorString(queryEmbedding);
List<Document> results = docMapper.searchByVector(q, 5);

6. 带过滤的 MyBatis 检索

<select id="searchInCategory" resultType="Document">
    SELECT id, content,
           embedding <![CDATA[ <=> ]]> #{queryVec}::vector AS distance
    FROM documents
    WHERE category = #{category}
    ORDER BY embedding <![CDATA[ <=> ]]> #{queryVec}::vector
    LIMIT #{topK}
</select>

7. 会话参数(ef_search)怎么设

想在查询前设置 hnsw.ef_search,可以在同一连接先执行:

try (Statement st = conn.createStatement()) {
    st.execute("SET hnsw.ef_search = 100");
}
// 再执行检索查询(同一连接生效)

MyBatis 里可用拦截器或在同一 SqlSession 里先执行一条 SET。注意连接池复用,必要时用 SET LOCAL 放事务里。

8. 用可选的 pgvector JDBC 支持(进阶)

pgvector 官方提供了 com.pgvector:pgvector Java 库,可注册 PGvector 类型,直接用 setObject

// 依赖:com.pgvector:pgvector
PGvector.addVectorType(conn);   // 注册类型
ps.setObject(2, new PGvector(embedding));  // 直接传 float[]

好处是不用手拼字符串。但理解第 2~5 节的"字符串"本质更重要——框架也是这么干的。

9. 生成向量在哪一步

注意:本章只讲Java 怎么读写向量。向量本身还是要先调 embedding 模型生成(第 24 章)。完整链路:

文本 ──调embedding模型(HTTP)──► float[] ──VectorUtils──► "[...]" ──MyBatis──► pgvector

而 Spring AI 就是把"调模型 + 拼字符串 + 入库/检索"这一整套都封装好了(见 Spring AI 文档)。

10. 一句话总结

裸 Java 操作 pgvector 的本质就是"向量当字符串 + ::vector 转型":写入拼成 [...]、检索用 <=> 排序;理解这层,Spring AI 的封装就一目了然。


🎉 第五阶段完成! 你已打通"生成 embedding → 分块 → 混合检索 → 重排 → RAG 原理 → Java 落地"的应用全链路。

➡️ 接下来有两条路:

  • 想做应用/RAG 工程化 → 转姊妹篇 docs/spring-ai-vector-learning/,用 Spring AI 把这些封装成积木。
  • 想继续深入数据库生产运维 → 进入第六阶段(第 30~37 章):质量评估、量化压缩、索引维护、生态选型、容量、监控、排查。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倒流时光三十年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值