初识PostgreSQL

pgsql相比mysql而言,更加的标准和强大。对sql标准的支持度很高,在mysql中偷懒的写法在这里会报错;其次是pgsql的功能丰富,有很多扩展是按需加载的。像是ai需要使用的向量数据库,有现成的pgvecotr扩展可以直接使用就。

认识pgsql

PGvector 是一个为 PostgreSQL 设计的开源扩展,支持存储和检索机器学习生成的嵌入向量。它提供多种功能,让用户既能进行精确最近邻搜索,也能进行近似最近邻搜索。该扩展旨在与 PostgreSQL 的其他特性(包括索引和查询)无缝协作。

集成springboot

在项目中添加 PgVectorStore 启动器依赖:

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
<version>2.0.0-M7</version>
<scope>compile</scope>
</dependency>

配置相应的yml文件:

1
2
3
4
5
6
7
8
9
spring:
ai:
vectorstore:
pgvector:
index-type: hnsw # 索引类型,hnsw 表示使用 HNSW(Hierarchical Navigable Small World)索引,特点是查询极快、召回率高,适合千万级以上的大规模向量检索。
distance-type: COSINE_DISTANCE # 指定向量相似度计算使用的距离度量方式。COSINE_DISTANCE 表示使用余弦距离(1 - 余弦相似度)。值越小,向量越相似,常用于文本语义搜索。
dimensions: 1024 # 定义向量列的维度数。1024 表示每个向量有 1024 个浮点数。text-embedding-v3实际生成的向量维度
initialize-schema: true # 开发环境设置为 true,方便快速启动。生产环境设置为 false,手动管理数据库 schema,避免意外变更。
remove-existing-vector-store-table: false # 保留现有表和数据

这段配置告诉 Spring AI 使用 pgvector 作为向量数据库,建 HNSW 索引、用余弦距离、向量维度 1024,启动时自动建表但不删除旧数据。

同时还要添加数据库依赖和springai的依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>2.0.0-M7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

然后配置ai和datasource

1
2
3
4
5
6
7
8
9
10
11
12
spring:
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/interview_guide
username: postgres
password: 123456
ai:
openai:
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: ${AI_BAILIAN_API_KEY}
embedding: # Embedding模型配置 - 使用text-embedding-v3
model: text-embedding-v3 # 指定生成文本向量时的模型

配置好后,启动springboot程序,去数据中就可以找到vector_store这张表。

将知识向量化并存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private static final int MAX_BATCH_SIZE = 10;
@Transactional
public void vectorizeAndStore(Long knowledgeBaseId, String content) {
log.info("开始向量化知识库: kbId={}, contentLength={}", knowledgeBaseId, content.length());
try {
// 1. 先删除该知识库的旧向量数据
deleteByKnowledgeBaseId(knowledgeBaseId);

// 2. 将文本分块, 使用 TokenTextSplitter 默认配置,每个 chunk 约 800 tokens,基于标点边界切分(无重叠)
List<Document> chunks = TokenTextSplitter.builder().build().apply(
List.of(new Document(content))
);

log.info("文本分块完成: {} 个chunks", chunks.size());

// 3. 为每个chunk添加metadata(知识库ID)
// 统一使用 String 类型存储,确保查询一致性
chunks.forEach(chunk -> chunk.getMetadata().put("kb_id", knowledgeBaseId.toString()));
// 4. 分批向量化并存储
int totalChunks = chunks.size();
int batchCount = (totalChunks + MAX_BATCH_SIZE - 1) / MAX_BATCH_SIZE; // 向上取整
log.info("开始分批向量化: 总共 {} 个chunks,分 {} 批处理,每批最多 {} 个",
totalChunks, batchCount, MAX_BATCH_SIZE);
for (int i = 0; i < batchCount; i++) {
int start = i * MAX_BATCH_SIZE;
int end = Math.min(start + MAX_BATCH_SIZE, totalChunks);
List<Document> batch = chunks.subList(start, end);
log.debug("处理第 {}/{} 批: chunks {}-{}", i + 1, batchCount, start + 1, end);
vectorStore.add(batch);
}
log.info("知识库向量化完成: kbId={}, chunks={}, batches={}",
knowledgeBaseId, totalChunks, batchCount);
} catch (Exception e) {
log.error("向量化知识库失败: kbId={}, error={}", knowledgeBaseId, e.getMessage(), e);
throw new BusinessException(ErrorCode.KNOWLEDGE_BASE_VECTORIZATION_FAILED,
"向量化知识库失败: " + e.getMessage());
}
}

使用junit执行后,就可以在表vector_store中看到向量化之后的数据了。

基于知识库相似度搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void query(){
try {
SearchRequest.Builder builder = SearchRequest.builder()
.query("向量")
.topK(Math.max(10, 1));

builder.similarityThreshold(0.6);

List<Document> results = vectorStore.similaritySearch(builder.build());

List<Document> limitedResults = results.stream()
.limit(10)
.collect(Collectors.toList());

for (Document limitedResult : limitedResults) {
System.out.println(limitedResult);
}

} catch (Exception e) {
e.printStackTrace();
}
}

参考

https://springdoc.tech/spring-ai/api/vectordbs/pgvector/