You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中MongoDB按最大值分组获取完整实体的方法

问题分析与解决思路

核心问题拆解

你的需求是按fileId分组,获取每个文件最新年份的完整FileRecord文档,当前代码存在两个关键问题:

  1. 分组逻辑错误:按fileId+year分组无法筛选出每个文件的最新年份记录,应该仅按fileId分组并取最大year
  2. 聚合结果映射错误:分组后的文档结构与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 23:55:19