Spring MongoDB 聚合中正确投影多个字段的实践指南

本文详解如何在 spring data mongodb 的 aggregation 中正确使用 projectionoperation 同时投影 `_id` 和分组统计字段(如 `countoffoousers`),解决因误用 `and()` 导致目标字段为 null 的常见问题。

在 Spring Data MongoDB 中执行聚合操作时,ProjectionOperation 是控制最终输出字段的关键环节。许多开发者(尤其是初学者)容易误以为调用 .and("fieldName") 即可“添加”字段到投影结果中——但事实并非如此:and(String) 方法仅用于定义新计算字段的表达式(如 and("age").multiply(2)),而非简单地包含已有字段。

你遇到的问题根源正在于此:

projectByIDandCount.and("countOfFooUsers"); // ❌ 无效!这不会将 countOfFooUsers 加入投影

该行代码未指定别名,也未触发字段包含逻辑,因此 countOfFooUsers 在最终文档中被忽略,导致 MAandUsers.countOfFooUsers 反序列化为 null。

✅ 正确做法是使用 .andInclude(...) ——这是 Spring Data MongoDB 专为“原样包含已存在字段”设计的语义化方法:

ProjectionOperation projectByIDandCount = project()
    .and("_id").as("defaultMasterAccountId")  // 将 _id 重命名为 defaultMasterAccountId
    .andInclude("countOfFooUsers");           // 显式包含 group 阶段生成的 countOfFooUsers 字段

完整修正后的聚合代码如下:

public Map getUsersByAccount() {
    MatchOperation filterByAccountId = match(new Criteria(ACCOUNT_ID).nin(Arrays.asList(null, "")));
    GroupOperation groupByMasterAccount = group(DEFAULT_MASTER_ACCOUNT_ID).count().as("countOfFooUsers");

    ProjectionOperation projectByIDandCount = project()
        .and("_id").as("defaultMasterAccountId")
        .andInclude("countOfFooUsers"); // ✅ 关键修复:显式包含分组字段

    Aggregation aggregation = newAggregation(
        filterByAccountId,
        groupByMasterAccount,
        projectByIDandCount
    );

    AggregationResults result = mongoTemplate
        .aggregate(aggregation, USERS_COLLECTION, MAandUsers.class);

    // 转换为 Map
    return result.getMappedResults().stream()
        .collect(Collectors.toMap(
            MAandUsers::getDefaultMasterAccountId,
            MAandUsers::getCountOfFooUsers
        ));
}

@Data
public static class MAandUsers {
    private Long defaultMasterAccountId;
    private Integer countOfFooUsers;
}

⚠️ 注意事项:

  • andInclude() 支持多字段:.andInclude("countOfFooUsers", "otherField");
  • 若需重命名多个字段,应分别使用 .and("fi

    eld").as("newName");
  • 确保 MAandUsers 类中的字段名与投影后的键名严格一致(区分大小写),否则反序列化失败;
  • 调试技巧:可先用 result.getRawResults() 查看原始 BSON 输出,验证字段是否存在及命名是否正确。

总结:Spring Data MongoDB 的 ProjectionOperation 并非“链式添加字段”的简易工具,而是具备明确语义的 DSL。牢记 andInclude() 用于保留现有字段、and(...).as(...) 用于重命名或计算字段——掌握这一区分,即可避免 90% 的投影空值问题。