跳到主要内容

pgvector:向量映射与相似性检索

发布:更新:阅读需 3 分钟

AI 时代的数据库应用越来越多地需要存储和检索向量数据。PostgreSQL 的 pgvector 扩展提供了高效的向量存储和相似性搜索能力,但在 Java 侧一直缺少优雅的映射方案。

dbVisitor 6.7.0 新增的 PgVectorTypeHandler 让你可以用 List<Float> 直接映射 pgvector 的 vector 类型,配合 Fluent API 实现完整的向量 CRUD 和 KNN 检索。

固定使用 6.8.0 的回归示例:GitHub / Gitee。

6.8.0 用法

以下示例已更新至 6.8.0:构造器向量排序参数会使用字段配置的 TypeHandler。字段配置 PgVectorTypeHandler 时,传入 List<Float>,不再使用旧例中的 PGobject 排序参数。详见 向量类型处理器。

认识 pgvector​

pgvector 是 PostgreSQL 的向量扩展,支持:

  • 存储高维向量(如 embedding)
  • L2 距离、余弦相似度、内积等相似性搜索
  • IVFFLAT 和 HNSW 索引加速

在 AI 应用中,文本 embedding、图片特征向量、推荐系统的用户向量等都需要存入数据库并执行最近邻搜索。

配置向量映射​

先在已安装 pgvector 的 PostgreSQL 中创建表:

CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE product_vector (
id integer PRIMARY KEY,
name text,
price numeric,
category text,
embedding vector(3)
);

映射定义​

@Table("product_vector")
public class ProductVector {
@Column(primary = true)
private Integer id;
private String name;
private BigDecimal price;
private String category;

@Column(typeHandler = PgVectorTypeHandler.class)
private List<Float> embedding;

// getters/setters...
}

只需在 @Column 注解上指定 typeHandler = PgVectorTypeHandler.class,即可实现 List<Float> 与 pgvector vector 类型的自动互转。

基本 CRUD​

LambdaTemplate lambda = new LambdaTemplate(dataSource);

// 插入向量数据
ProductVector product = new ProductVector();
product.setId(1);
product.setName("iPhone");
product.setPrice(new BigDecimal("299"));
product.setCategory("electronics");
product.setEmbedding(Arrays.asList(0.1f, 0.2f, 0.3f));

lambda.insert(ProductVector.class)
.applyEntity(product)
.executeSumResult();

// 查询并获取向量
ProductVector loaded = lambda.query(ProductVector.class)
.eq(ProductVector::getId, 1)
.queryForObject();

List<Float> embedding = loaded.getEmbedding();
// [0.1, 0.2, 0.3]

KNN 相似性检索​

传入与字段映射一致的 List<Float>,由配置的 PgVectorTypeHandler 同时处理写入向量和查询向量。

// 查询向量
List<Float> queryVector = List.of(0.15F, 0.25F, 0.35F);

// L2 距离排序(欧氏距离)
List<ProductVector> nearest = lambda.query(ProductVector.class)
.orderByL2(ProductVector::getEmbedding, queryVector) // 按 L2 距离升序
.initPage(5, 0)
.queryForList();

// 余弦相似度排序
List<ProductVector> similar = lambda.query(ProductVector.class)
.orderByCosine(ProductVector::getEmbedding, queryVector)
.initPage(5, 0)
.queryForList();

// 内积排序
List<ProductVector> ipResults = lambda.query(ProductVector.class)
.orderByIP(ProductVector::getEmbedding, queryVector)
.initPage(5, 0)
.queryForList();

// 通用接口 — 枚举驱动
List<ProductVector> results = lambda.query(ProductVector.class)
.orderByMetric(MetricType.L2, ProductVector::getEmbedding, queryVector)
.initPage(10, 0)
.queryForList();

向量与标量查询​

// 在价格范围内搜索最相似的商品
List<ProductVector> results = lambda.query(ProductVector.class)
.rangeBetween(ProductVector::getPrice, 100, 500)
.eq(ProductVector::getCategory, "electronics")
.orderByL2(ProductVector::getEmbedding, queryVector)
.initPage(10, 0)
.queryForList();

实现原理​

PgVectorTypeHandler 的实现非常简洁:

  • 写入:将 List<Float> 序列化为 pgvector 文本格式 [0.1,0.2,0.3],以 Types.OTHER 传入 PreparedStatement
  • 读取:将 pgvector 返回的字符串 [0.1,0.2,0.3] 解析为 List<Float>
// 写入
ps.setObject(i, "[0.1,0.2,0.3]", Types.OTHER);

// 读取
String val = rs.getString(columnName); // "[0.1,0.2,0.3]"
List<Float> vector = parseVector(val); // [0.1f, 0.2f, 0.3f]

这种基于文本格式的方案与 pgvector 的官方协议一致,不依赖任何额外的 Java 客户端库。