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 实体类
向量字段用 String 或 float[] 都行。用 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 章):质量评估、量化压缩、索引维护、生态选型、容量、监控、排查。

1085

被折叠的 条评论
为什么被折叠?



