
本文详解如何在 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 MapgetUsersByAccount() { 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("field").as("newName");
- 确保 MAandUsers 类中的字段名与投影后的键名严格一致(区分大小写),否则反序列化失败;
- 调试技巧:可先用 result.getRawResults() 查看原始 BSON 输出,验证字段是否存在及命名是否正确。
总结:Spring Data MongoDB 的 ProjectionOperation 并非“链式添加字段”的简易工具,而是具备明确语义的 DSL。牢记 andInclude() 用于保留现有字段、and(...).as(...) 用于重命名或计算字段——掌握这一区分,即可避免 90% 的投影空值问题。










