1. 问题背景
在开发基于 Spring Boot + LangChain4j + PostgreSQL (pgvector) 的 RAG(检索增强生成)系统时,我遇到了一个诡异的现象:
- Java 后端日志显示向量数据已成功发送(
add()方法调用完成)。 - 没有任何明显的 Java 异常抛出。
- 但数据库中
aroma_vectors表始终为空。
2. 排查过程:拨开迷雾
最初我怀疑是事务回滚或者异步写入的问题,但在增加了 @Rollback(false) 并检查了事务配置后,问题依旧。
最终,我通过查看 PostgreSQL 数据库的系统运行日志 (pg_log),抓到了“现行犯”:
ERROR: column "embedding_id" of relation "aroma_vectors" does not existSTATEMENT: INSERT INTO aroma_vectors (embedding_id, embedding, text, metadata) VALUES ...
3. 根本原因分析
问题的核心在于:LangChain4j 的 PgVectorEmbeddingStore 驱动采用了“约定优于配置”的设计,其内部 SQL 语句硬编码了字段名。
驱动的预设约定
当你调用 embeddingStore.add() 时,驱动内部生成的 INSERT 或 UPSERT 语句默认寻找以下列名:
embedding_id: 存储 UUID 格式的主键。embedding: 存储向量数据(vector 类型)。text: 存储原始文本段落(对应 LangChain4j 的content)。metadata: 存储关联的 JSONB 元数据。
我的数据库定义
我最初定义表结构:
-- 4. 向量存储表:用于 RAG 检索
CREATE TABLE aroma_vectors (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 使用 UUID
content TEXT NOT NULL, -- 语义化后的拼接文本
metadata JSONB, -- 存放元数据 (如对应的术语ID)
embedding vector(1536) -- 向量列 (维度 1536 对应 DeepSeek/OpenAI)
);
COMMENT ON TABLE aroma_vectors IS 'AI 向量存储表,用于 RAG 语义搜索';
结果
驱动将 SQL 发送给数据库,PostgreSQL 发现 SQL 语句中引用的 embedding_id 在表中不存在,直接 Aborted(打回) 了事务。由于这是一个批量或异步操作,Java 端的报错有时会被框架层的异步机制掩盖,导致表面看起来“发送成功”但实际“颗粒无收”。
4. 解决方案
调整数据库表定义,使其完全符合 LangChain4j 的驱动约定。
标准的 pgvector 表初始化脚本:
CREATE TABLE aroma_vectors (
embedding_id UUID PRIMARY KEY, -- 必须叫这个名字
embedding vector(1024), -- 维度需与 Embedding 模型一致
text TEXT, -- 必须叫 text
metadata JSONB -- 必须叫 metadata
);
5. 经验总结
- 看日志要看全套:当 Java 日志无法解释逻辑错误时,数据库运行日志是最后的真相。
- 警惕框架约定:在使用 LangChain4j、Spring AI 等新兴 AI 框架时,它们往往对数据库 Schema 有默认的强约定。在初始化
EmbeddingStore时,务必确认其底层的 SQL 实现。 - 向量维度对齐:除了字段名,
vector(N)中的维度N必须与 Embedding 模型(如 DeepSeek 或 OpenAI)的输出严格对齐,否则也会触发类似的写入失败。
💡 结语
在面试时,如果面试官问到你在这个环节遇到的困难,你可以说:“在集成 LangChain4j 和 pgvector 时,发现框架层对数据库字段命名有约定优于配置(Convention over Configuration)的倾向,通过查看框架生成的原始 SQL 报错日志,定位到了字段名不匹配的问题,并按照框架规范统一了 Schema 定义。” 这能体现你具备阅读底层日志和解决框架集成冲突的能力。
LangChain4jConfig配置:
package com.aroma.config;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author yexiebao
* @date 2026/1/22
*/
@Configuration
public class LangChain4jConfig {
// 从application.yml读取PGVector配置
@Value("${pgvector.host}")
private String pgVectorHost;
@Value("${pgvector.port}")
private int pgVectorPort;
@Value("${pgvector.database}")
private String pgVectorDatabase;
@Value("${pgvector.user}")
private String pgVectorUser;
@Value("${pgvector.password}")
private String pgVectorPassword;
@Value("${pgvector.table}")
private String pgVectorTable;
@Value("${pgvector.dimension}")
private int pgVectorDimension;
/**
* 配置PGVector存储,从配置文件读取连接信息
*/
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return PgVectorEmbeddingStore.builder()
.host(pgVectorHost) // 读取配置文件
.port(pgVectorPort) // 读取配置文件
.database(pgVectorDatabase) // 读取配置文件
.user(pgVectorUser) // 读取配置文件
.password(pgVectorPassword) // 读取配置文件
.table(pgVectorTable) // 读取配置文件(你的表名aroma_vectors)
.dimension(pgVectorDimension)// 读取配置文件(1024维)
.build();
}
}