向量查询
在 AI 和机器学习场景中,文本、图片、音频等非结构化数据通常会被模型编码为高维向量(也称 Embedding)。
向量之间的距离可以衡量原始数据的语义相似度 —— 距离越近,语义越相似。
向量查询的核心问题是:给定一个目标向量,在数据库中找到与它距离最近的记录。
距离度量
不同的度量方式适用于不同的场景,dbVisitor 支持 6 种度量:
- 不确定时选 L2(欧氏距离),它是最通用的度量方式。
- 文本语义搜索选 Cosine,它只关注方向不关注向量长度,适合归一化后的 Embedding。
- 推荐/排序场景选 IP(内积),当向量已归一化时,IP 结果等价于 Cosine 相似度。
两种查询模式
dbVisitor 提供两种向量搜索模式,分别对应 SQL 中的 ORDER BY 和 WHERE:
| 对比项 | KNN (orderBy*) | Range (vectorBy*) |
|---|---|---|
| SQL 位置 | ORDER BY | WHERE |
| 返回数量 | 固定 K 条(需配合 initPage) | 不固定,取决于阈值 |
| 适用场景 | "找最相似的 N 个" | "找所有距离在范围内的" |
| 可组合性 | 可追加 WHERE 条件做预过滤 | 可与其他 WHERE 条件自由组合 |
准备工作
1. 建表
以 PostgreSQL + pgvector 为例,需要安装 vector 扩展并创建向量列:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE product_vector (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
embedding vector(128) -- 128 维向量
);
2. 实体映射
@Table("product_vector")
public class ProductVector {
@Column(primary = true)
private Integer id;
private String name;
@Column(typeHandler = PgVectorTypeHandler.class)
private List<Float> embedding;
// getter / setter ...
}
- 向量字段使用
List<Float>表示。 - 需要为向量字段指定
typeHandler来处理List<Float>与数据库向量类型之间的转换。
PgVectorTypeHandler 是针对 PostgreSQL pgvector 的实现,利用 PGobject 进行 List<Float> 与 vector 类型的互转。
如果使用其他数据库(如 Milvus),需要使用对应的 TypeHandler。
3. 向量参数格式
在 KNN 排序查询中(orderBy* 系列),向量参数需要传递数据库能识别的类型。
以 pgvector 为例,不能直接传递 List<Float>,需要包装为 PGobject:
PGobject vectorParam = new PGobject();
vectorParam.setType("vector");
vectorParam.setValue("[0.1,0.2,0.3,...]"); // pgvector 文本格式
在 Range 过滤查询中(vectorBy* 系列),向量参数会经过实体映射的 TypeHandler 自动转换,因此可以直接传 List<Float>。
KNN 排序查询
使用 orderBy* 方法按向量距离进行排序,返回距离目标最近的 K 条记录。
L2 欧氏距离
LambdaTemplate lambda = ...
Object target = ...; // 目标向量(PGobject 或数据库对应类型)
List<ProductVector> results = lambda.query(ProductVector.class)
.orderByL2(ProductVector::getEmbedding, target)
.queryForList();
// 对应的 SQL(pgvector):
// SELECT * FROM product_vector ORDER BY embedding <-> ? ASC
Cosine 余弦距离
List<ProductVector> results = lambda.query(ProductVector.class)
.orderByCosine(ProductVector::getEmbedding, target)
.queryForList();
// 对应的 SQL(pgvector):
// SELECT * FROM product_vector ORDER BY embedding <=> ? ASC
IP 内积距离
List<ProductVector> results = lambda.query(ProductVector.class)
.orderByIP(ProductVector::getEmbedding, target)
.queryForList();
// 对应的 SQL(pgvector):
// SELECT * FROM product_vector ORDER BY embedding <#> ? ASC
pgvector 的 <#> 运算符返回负内积。排序后,内积最大(最相似)的记录排在最前面。
Top-K 查询
配合 initPage 实现只返回最近的 K 条记录:
int topK = 5;
List<ProductVector> results = lambda.query(ProductVector.class)
.orderByL2(ProductVector::getEmbedding, target)
.initPage(topK, 0) // 只取前 5 条
.queryForList();
通用度量接口
通过 orderByMetric 方法可以使用 MetricType 枚举动态指定度量方式:
import net.hasor.dbvisitor.lambda.core.MetricType;
List<ProductVector> results = lambda.query(ProductVector.class)
.orderByMetric(MetricType.L2, ProductVector::getEmbedding, target)
.queryForList();
全部可用的度量方式参见上方 距离度量 章节。
| MetricType | 快捷方法 | pgvector 运算符 |
|---|---|---|
MetricType.L2 | orderByL2 | <-> |
MetricType.COSINE | orderByCosine | <=> |
MetricType.IP | orderByIP | <#> |
MetricType.HAMMING | orderByHamming | <~> |
MetricType.JACCARD | orderByJaccard | <%> |
MetricType.BM25 | orderByBM25 | <?> |
Range 范围过滤
使用 vectorBy* 方法只返回到目标向量距离小于阈值(threshold)的记录。它属于 WHERE 条件,可与其他条件自由组合。
L2 距离过滤
List<Float> target = ...; // 可直接使用 List<Float>
double threshold = 5.0;
List<ProductVector> results = lambda.query(ProductVector.class)
.vectorByL2(ProductVector::getEmbedding, target, threshold)
.queryForList();
// 对应的 SQL(pgvector):
// SELECT * FROM product_vector WHERE embedding <-> ? < ?
Cosine 距离过滤
List<ProductVector> results = lambda.query(ProductVector.class)
.vectorByCosine(ProductVector::getEmbedding, target, 0.1)
.queryForList();
// 对应的 SQL(pgvector):
// SELECT * FROM product_vector WHERE embedding <=> ? < ?
IP 距离过滤
List<ProductVector> results = lambda.query(ProductVector.class)
.vectorByIP(ProductVector::getEmbedding, target, -50.0)
.queryForList();
vectorBy* 的向量参数会经过实体映射的 TypeHandler 自动转换,因此可以直接传递 List<Float>。
orderBy* 的向量参数直接进入 SQL 参数绑定,需要传递数据库能识别的类型(如 PGobject)。
条件开关
所有 vectorBy* 方法都支持通过第一个 boolean 参数控制条件是否生效:
boolean enableVectorFilter = ...;
List<ProductVector> results = lambda.query(ProductVector.class)
.vectorByL2(enableVectorFilter, ProductVector::getEmbedding, target, threshold)
.queryForList();
// enableVectorFilter = false 时,向量过滤条件不会出现在 SQL 中
组合查询
向量查询可以和标量条件自由组合,实现先过滤再排序或先排序再过滤。
KNN + 标量过滤
List<ProductVector> results = lambda.query(ProductVector.class)
.likeRight(ProductVector::getName, "Cat-A") // 标量条件
.orderByL2(ProductVector::getEmbedding, target) // 向量排序
.initPage(3, 0) // Top-3
.queryForList();
// 对应的 SQL(pgvector):
// SELECT * FROM product_vector
// WHERE name LIKE 'Cat-A%'
// ORDER BY embedding <-> ?
// LIMIT 3
Range + 标量过滤
List<ProductVector> results = lambda.query(ProductVector.class)
.likeRight(ProductVector::getName, "R-A") // 标量条件
.vectorByL2(ProductVector::getEmbedding, target, 6.0) // 向量范围过滤
.queryForList();
// 对应的 SQL(pgvector):
// SELECT * FROM product_vector
// WHERE name LIKE 'R-A%'
// AND embedding <-> ? < ?
基础操作
向量数据的增删改操作与普通实体完全一致,通过 TypeHandler 自动处理 List<Float> 的序列化和反序列化。
ProductVector p = new ProductVector();
p.setId(1001);
p.setName("sample");
p.setEmbedding(Arrays.asList(0.1f, 0.2f, 0.3f, ...)); // 128 维
lambda.insert(ProductVector.class)
.applyEntity(p)
.executeSumResult();
List<Float> newVec = Arrays.asList(0.9f, 0.8f, 0.7f, ...);
lambda.update(ProductVector.class)
.eq(ProductVector::getId, 1001)
.updateTo(ProductVector::getEmbedding, newVec)
.doUpdate();
ProductVector loaded = lambda.query(ProductVector.class)
.eq(ProductVector::getId, 1001)
.queryForObject();
List<Float> vec = loaded.getEmbedding(); // 自动反序列化为 List<Float>
API 参考
KNN 排序(QueryFunc 接口)
| 方法 | 说明 |
|---|---|
orderByL2(P property, Object vector) | 按 L2 距离排序 |
orderByCosine(P property, Object vector) | 按 Cosine 距离排序 |
orderByIP(P property, Object vector) | 按 IP 距离排序 |
orderByHamming(P property, Object vector) | 按 Hamming 距离排序 |
orderByJaccard(P property, Object vector) | 按 Jaccard 距离排序 |
orderByBM25(P property, Object vector) | 按 BM25 评分排序 |
orderByMetric(MetricType, P property, Object vector) | 通过枚举指定度量方式 |
Range 过滤(QueryCompare 接口)
| 方法 | 说明 |
|---|---|
vectorByL2(P property, Object vector, Number threshold) | L2 距离小于阈值 |
vectorByCosine(P property, Object vector, Number threshold) | Cosine 距离小于阈值 |
vectorByIP(P property, Object vector, Number threshold) | IP 距离小于阈值 |
vectorByHamming(P property, Object vector, Number threshold) | Hamming 距离小于阈值 |
vectorByJaccard(P property, Object vector, Number threshold) | Jaccard 距离小于阈值 |
vectorByBM25(P property, Object vector, Number threshold) | BM25 距离小于阈值 |
所有 vectorBy* 方法均支持 (boolean test, P property, Object vector, Number threshold) 形式的重载,用于动态控制条件是否生效。
相关的类
- net.hasor.dbvisitor.lambda.core.MetricType
- net.hasor.dbvisitor.lambda.core.QueryFunc
- net.hasor.dbvisitor.lambda.core.QueryCompare