Spring Boot中MongoDB按最大值分组获取完整实体的方法
问题分析与解决思路
核心问题拆解
你的需求是按fileId分组,获取每个文件最新年份的完整FileRecord文档,当前代码存在两个关键问题:
- 分组逻辑错误:按
fileId+year分组无法筛选出每个文件的最新年份记录,应该仅按fileId分组并取最大year - 聚合结果映射错误:分组后的文档结构与
FileRecord不匹配,导致Spring Data MongoDB尝试将分组的复合_id(文档类型)映射到ObjectId类型的id字段,触发类型转换异常
正确实现步骤
1. 定义分组结果中间类
分组阶段的输出是fileId和对应最大年份,需要一个中间类接收:
@Data class FileYearGroup { @Field("_id") private String fileId; @Field("max_year") private Integer maxYear; }
2. 完整聚合查询实现
通过Lookup关联原集合,获取符合条件的完整文档:
// 1. 匹配条件(保留你的原有逻辑) MatchOperation match = match( Criteria.where("userId").in(userIds) .and("status").in(EnumStatus.availableStatues()) ); // 2. 按fileId分组,取每个文件的最大年份 GroupOperation group = group("fileId") .max("year").as("max_year"); // 3. 关联原集合,找到fileId匹配且year等于max_year的文档 LookupOperation lookup = lookup("fileRecord", "_id", "fileId", "matchedRecords") .matching(Criteria.where("year").expression("$$max_year")); // 4. 展开关联得到的数组(每个分组只会有一个匹配项) UnwindOperation unwind = unwind("matchedRecords"); // 5. 将匹配到的完整文档作为根文档 ReplaceRootOperation replaceRoot = replaceRoot("matchedRecords"); // 组装聚合管道 Aggregation aggregation = newAggregation(match, group, lookup, unwind, replaceRoot); // 执行聚合,映射为FileRecord集合 AggregationResults<FileRecord> aggregateResult = mongoOperations.aggregate( aggregation, "fileRecord", // 可替换为FileRecord.class,需确保实体类配置了正确的集合名称 FileRecord.class ); Collection<FileRecord> records = aggregateResult.getMappedResults();
3. 对应的MongoDB原生查询语句
帮助你理解聚合管道的逻辑:
db.fileRecord.aggregate([ // 匹配条件 { $match: { userId: { $in: [/* userIds数组 */] }, status: { $in: [/* 可用状态枚举值 */] } } }, // 按fileId分组取最大年份 { $group: { _id: "$fileId", max_year: { $max: "$year" } } }, // 关联原集合获取完整文档 { $lookup: { from: "fileRecord", let: { fileId: "$_id", maxYear: "$max_year" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$fileId", "$$fileId"] }, { $eq: ["$year", "$$maxYear"] } ] } }} ], as: "matchedRecords" } }, // 展开数组 { $unwind: "$matchedRecords" }, // 替换为完整文档 { $replaceRoot: { newRoot: "$matchedRecords" } } ])
异常原因说明
你之前的聚合结果是分组后的文档,结构为:
{ "_id": { "fileId": "xxx", "year": 2024 }, "max_year": 2024 }
Spring Data MongoDB尝试将这个复合_id(文档类型)映射到FileRecord的ObjectId类型id字段,因此抛出readObjectId can only be called when CurrentBSONType is OBJECT_ID异常。通过Lookup+ReplaceRoot可以将结果还原为原FileRecord的结构,解决类型映射问题。
内容的提问来源于stack exchange,提问作者Volodymyr Lykhvar
相关产品推荐
相关产品推荐

