Spring Data MongoDB聚合查询嵌套关联对象返回null问题
核心问题原因及修复
关联字段类型不匹配,$lookup无法命中关联数据
MongoDB$lookup阶段做关联匹配时严格校验字段类型,你当前product_attributes集合中存储的productId、attributeId是String类型,而products、attributes集合的主键_id是ObjectId类型,二者字面量相同也不会判定为相等,因此关联结果始终为空数组,后续$unwind处理后自然拿不到关联对象。
修复方式:在关联查询前先将字符串格式的id转换为ObjectId类型,在聚合管道中加入字段转换阶段即可,代码如下:import org.springframework.data.mongodb.core.aggregation.AddFieldsOperation import org.springframework.data.mongodb.core.aggregation.ConvertOperators.ToObjectId // 放在match阶段之后、第一个lookup之前 val convertIdFields = Aggregation.addFields() .addField("productId").withValue(ToObjectId.toObjectId("\$productId")) .addField("attributeId").withValue(ToObjectId.toObjectId("\$attributeId")) .build()调整后聚合管道的顺序为:
match(id) -> convertIdFields -> lookup(products) -> unwind(product) -> lookup(attributes) -> unwind(attribute)。实体类注解配置错误,聚合结果无法正确映射
你在product、attribute字段上加了@get:Transient和@Value("null")注解:@Transient的作用是标记字段完全不参与MongoDB文档的序列化、反序列化流程,哪怕聚合结果中存在这两个字段,框架映射实体时也会直接忽略;@Value("null")会强制给字段赋值null,最终导致两个字段始终为空。
修复方式:直接删除两个字段上的@get:Transient和@Value("null")注解。如果担心这两个计算字段被写入product_attributes集合,可以给字段加@Field(write = false)配置,标识字段仅用于读映射、不参与写入即可。
优化点
当前unwind方法未配置空值保留,若某条product_attribute数据关联不到对应的product或attribute,会被unwind直接过滤导致整条数据丢失,建议调整unwind实现开启空值保留:
fun unwind(field: String) = Aggregation.unwind(field, true)
第二个参数传true即开启preserveNullAndEmptyArrays配置,关联失败时对应嵌套字段返回null,不会过滤整条主数据。
排查顺序
- 先在MongoDB客户端直接执行聚合语句,跳过代码映射环节,确认原生查询是否能返回正确的关联对象,先排查数据、管道逻辑问题
- 代码中打印MongoDB驱动执行的聚合原始BSON结果,确认返回结果中是否存在
product、attribute字段,排查是查询阶段问题还是实体映射阶段问题
内容的提问来源于stack exchange,提问作者Mykyta Frolov

