Spring AI Qdrant 配置了 initializeSchema 却不自动建集合:build() 只是 new,建集合在 afterPropertiesSet()
Spring AI Qdrant 配了 initializeSchema 却不自动建集合
背景
使用 Spring AI 2.0.1 的 spring-ai-qdrant-store,按官方示例手动构建向量库:
this.qdrantVectorStore = QdrantVectorStore.builder(qdrantClient, embeddingModel)
.collectionName(vector.getId().toString())
.contentFieldName("content")
.initializeSchema(true) // 我以为这会帮我建集合
.build();
initializeSchema(true) 明明配了,但第一次写入时就报:
Collection '<UUID>' doesn't exist!
集合从来没有被自动创建。而网上示例、Spring AI 的自动配置(starter)看起来都是开箱即用、全自动建集合——为什么到我这里就失效?
排查过程
连接没问题(gRPC 能通,否则报的是连接错误而不是集合不存在),问题一定出在初始化时机上。拆开 spring-ai-qdrant-store-2.0.1-sources.jar 看 QdrantVectorStore 源码,三个关键点:
QdrantVectorStore实现了InitializingBean(extends AbstractObservationVectorStore implements InitializingBean)。Builder.build()只是new QdrantVectorStore(this),什么都不执行;initializeSchema(true)也只是把这个布尔值存进字段。- 真正建集合的代码全部在
afterPropertiesSet()里:
@Override
public void afterPropertiesSet() throws Exception {
if (!this.initializeSchema) {
return; // 没开开关,直接跳过
}
if (!isCollectionExists()) { // 先 listCollections 查一下
var vectorParams = VectorParams.newBuilder()
.setDistance(Distance.Cosine)
.setSize(this.embeddingModel.dimensions())
.build();
this.qdrantClient.createCollectionAsync(this.collectionName, vectorParams).get();
}
}
根因
afterPropertiesSet() 是 Spring 的 InitializingBean 生命周期回调:只有对象被注册成 Spring 管理的 Bean、由容器在初始化阶段调用,它才会执行。
我们这里是手动 new 出来的普通对象(构造函数里 build(),对象不属于 Spring 容器),容器永远不会来调 afterPropertiesSet(),于是:
initializeSchema(true)只是立了个"开关";- 开关永远没人去拨动;
- 集合从未创建;
- 第一次
upsert打过去,Qdrant 返回Collection doesn't exist!。
"配了自动初始化"和"真正执行初始化"之间,差了一个容器生命周期回调。
为什么 starter 自动配置就能用
Spring AI 的自动配置(spring-ai-starter-vector-store-qdrant)会把 store 声明成一个 @Bean:
@Bean
public VectorStore vectorStore(QdrantClient client, EmbeddingModel model) { ... }
Bean 是容器管理的,初始化时容器自动调用 afterPropertiesSet() → 集合被创建。这就是"依赖 starter、自动注入就一切正常;手动 new 就完全没有"的原因——不是配置差异,是谁在调用生命周期回调的差异。
解决方案
方案一(推荐):build() 之后手动补调 afterPropertiesSet()
它本身就是幂等的:集合已存在就跳过,不存在才创建。每次构建 store 调一次都安全。
this.qdrantVectorStore = QdrantVectorStore.builder(qdrantClient, embeddingModel)
.collectionName(vector.getId().toString())
.contentFieldName("content")
.initializeSchema(true)
.build();
try {
this.qdrantVectorStore.afterPropertiesSet(); // 手动触发建集合
} catch (Exception e) {
throw new RuntimeException("初始化Qdrant集合失败: " + vector.getId(), e);
}
注意 afterPropertiesSet() 声明了 throws Exception,必须 try/catch。
方案二:不走回调,显式建集合
initializeSchema(false),直接用原始 QdrantClient 自己完成"查集合 → 建集合":
String name = vector.getId().toString();
if (qdrantClient.listCollectionsAsync().get().stream().noneMatch(name::equals)) {
qdrantClient.createCollectionAsync(name, VectorParams.newBuilder()
.setDistance(Distance.Cosine)
.setSize(dim) // 嵌入模型维度
.build()).get();
}
效果等同源码内部那段逻辑,只是把控制权拿回自己手里。
方案三:交给容器(不推荐本场景)
把 store 声明成 @Bean,让容器自动调回调。但集合名是 vector.getId() 动态生成的(一条向量一个集合),固定 @Bean 声明并不适配。
总结
build()≠ 初始化:Spring AI 向量库的initializeSchema只是开关,真正建集合/建索引的逻辑在InitializingBean.afterPropertiesSet()。- 回调只有容器调:手动
new的对象不是 Spring Bean,生命周期回调永远不会执行。 - 一句话排查套路:如果某个组件"starter 自动注入能用、手动 new 就失效",先怀疑它的初始化是不是挂在
afterPropertiesSet()/@PostConstruct这类容器生命周期回调上。
延伸:这不是 Qdrant 独有
RedisVectorStore(1.0.x)是同一个设计:afterPropertiesSet() 里才是 FT.CREATE 建索引。依赖 starter 自动配置索引自动建好,手动 new RedisVectorStore 则完全没有——套路完全一致,解法也一样:手动补调 afterPropertiesSet(),或显式建索引。

浙公网安备 33010602011771号