Spring 数据 - MongoDB - 聚合如何投影分组参考文档的所有字段
Spring Data - MongoDB - Aggregation how to project all fields of grouped reference document
我有 2 个文档,主题和评论。每个主题都有很多评论,文档如下所示:
@Document
public class Topic {
@Id
private String id;
@Indexed(unique = true)
@NotBlank
private String title;
}
@Document
public class Comment {
@NotBlank
private String text;
@Indexed
@NotBlank
private String topic; // id of topic
@CreatedDate
@Indexed
private LocalDateTime createdDate;
}
所以我实际上在评论中保存了 Topic 的 id 引用。
这是我的聚合场景:列出今天收到最多评论的主题。所以三件事:
- 获取今天的所有评论(MatchOperation)
- 按主题分组并汇总评论 (GroupOperation)
- 按此总和排序 (SortOperation)
这是目前的代码:
MatchOperation filterCreatedDate = match(Criteria.where("createdDate").gte(today.atStartOfDay()).lt(today.plusDays(1).atStartOfDay()));
GroupOperation groupByTopic = group("topic").count().as("todaysCommentsCount");
SortOperation sortByTodaysCommentsCount = sort(Sort.Direction.DESC, "todaysCommentsCount");
Aggregation aggregation = newAggregation(filterCreatedDate, groupByTopic, sortByTodaysCommentsCount);
AggregationResults<TopTopic> result = mongoTemplate.aggregate(aggregation, Comment.class, TopTopic.class);
这是一个特殊的Class输出:
public class TopTopic {
private int todaysCommentsCount;
}
这是我的聚合输出,其中只有一个主题:
{
"mappedResults": [
{
"todaysCommentsCount": 3
}
],
"rawResults": {
"results": [
{
"_id": "5dbdca8112a617031728c417", // topic id
"todaysCommentsCount": 3
}
],
"ok": 1.0
},
"serverUsed": null,
"uniqueMappedResult": {
"todaysCommentsCount": 1
}
}
我以为我实际上已经很接近了,但不知何故只有当我只有一个主题时它才有效。当有来自多个主题的评论应该创建多个组时,我收到此错误:
Could not write JSON: Expected unique result or null, but got more than one!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Expected unique result or null, but got more than one! (through reference chain: org.springframework.data.mongodb.core.aggregation.AggregationResults[\"uniqueMappedResult\"]
.. 虽然我没有调用任何 getUniqueMappedResult 方法。
我做错了什么?
其次,我怎样才能摆脱我的输出 class TopTopic,而不是 return 使用 todaysCommentsCount 扩展的原始主题值,而不创建特殊输出 class?
感谢您的帮助。
第一部分
您正在将 AggregationResults
发送回调用者,其中 jackson 正在序列化为 json 并且在调用 getUniqueMappedResult
.
时失败
将主题字段 _id 添加到 TopTopic
并读取 AggregationResults
中的映射结果。
List<TopTopic> topTopics = result.getMappedResults()
你的输出看起来像
[
{
"_id": "5dbdca8112a617031728c417",
"todaysCommentsCount": 3
}
]
第二部分
您可以使用 $$ROOT
变量和 $first
在 $group
阶段映射整个文档,然后 $replaceRoot
提升合并文档( $mergeObjects
合并 doc
和 todaysCommentCount
).
将 todaysCommentsCount
添加到主题 class。
类似
MatchOperation filterCreatedDate = match(Criteria.where("createdDate").gte(today.atStartOfDay()).lt(today.plusDays(1).atStartOfDay()));
GroupOperation groupByTopic = group("topic").first("$$ROOT").as("doc").count().as("todaysCommentsCount");
SortOperation sortByTodaysCommentsCount = sort(Sort.Direction.DESC, "todaysCommentsCount");
ReplaceRootOperation replaceRoot = Aggregation.replaceRoot().withValueOf(ObjectOperators.valueOf("doc").mergeWith(new Document("todaysCommentsCount", "$todaysCommentsCount")));
Aggregation aggregation = newAggregation(filterCreatedDate, groupByTopic, sortByTodaysCommentsCount, replaceRoot);
AggregationResults<TopTopic> result = mongoTemplate.aggregate(aggregation, Comment.class, Topic.class);
List<Topic> topTopics = result.getMappedResults();
你的输出看起来像
[
{
"_id": "5dbdca8112a617031728c417",
"title" : "topic",
"todaysCommentsCount": 3
}
]
更新(添加 $lookup 阶段以拉入主题字段)
MatchOperation filterCreatedDate = match(Criteria.where("createdDate").gte(today.atStartOfDay()).lt(today.plusDays(1).atStartOfDay()));
GroupOperation groupByTopic = group("topic").count().as("todaysCommentsCount");
SortOperation sortByTodaysCommentsCount = sort(Sort.Direction.DESC, "todaysCommentsCount");
LookupOperation lookupOperation = LookupOperation.newLookup().
from("topic").
localField("_id").
foreignField("_id").
as("topic");
ReplaceRootOperation replaceRoot = Aggregation.replaceRoot().withValueOf(ObjectOperators.valueOf("todaysCommentsCount").mergeWithValuesOf(ArrayOperators.arrayOf("topic").elementAt(0)));
Aggregation aggregation = newAggregation(filterCreatedDate, groupByTopic, sortByTodaysCommentsCount, lookupOperation, replaceRoot);
AggregationResults<TopTopic> result = mongoTemplate.aggregate(aggregation, Comment.class, Topic.class);
List<Topic> topTopics = result.getMappedResults();
我有 2 个文档,主题和评论。每个主题都有很多评论,文档如下所示:
@Document
public class Topic {
@Id
private String id;
@Indexed(unique = true)
@NotBlank
private String title;
}
@Document
public class Comment {
@NotBlank
private String text;
@Indexed
@NotBlank
private String topic; // id of topic
@CreatedDate
@Indexed
private LocalDateTime createdDate;
}
所以我实际上在评论中保存了 Topic 的 id 引用。
这是我的聚合场景:列出今天收到最多评论的主题。所以三件事:
- 获取今天的所有评论(MatchOperation)
- 按主题分组并汇总评论 (GroupOperation)
- 按此总和排序 (SortOperation)
这是目前的代码:
MatchOperation filterCreatedDate = match(Criteria.where("createdDate").gte(today.atStartOfDay()).lt(today.plusDays(1).atStartOfDay()));
GroupOperation groupByTopic = group("topic").count().as("todaysCommentsCount");
SortOperation sortByTodaysCommentsCount = sort(Sort.Direction.DESC, "todaysCommentsCount");
Aggregation aggregation = newAggregation(filterCreatedDate, groupByTopic, sortByTodaysCommentsCount);
AggregationResults<TopTopic> result = mongoTemplate.aggregate(aggregation, Comment.class, TopTopic.class);
这是一个特殊的Class输出:
public class TopTopic {
private int todaysCommentsCount;
}
这是我的聚合输出,其中只有一个主题:
{
"mappedResults": [
{
"todaysCommentsCount": 3
}
],
"rawResults": {
"results": [
{
"_id": "5dbdca8112a617031728c417", // topic id
"todaysCommentsCount": 3
}
],
"ok": 1.0
},
"serverUsed": null,
"uniqueMappedResult": {
"todaysCommentsCount": 1
}
}
我以为我实际上已经很接近了,但不知何故只有当我只有一个主题时它才有效。当有来自多个主题的评论应该创建多个组时,我收到此错误:
Could not write JSON: Expected unique result or null, but got more than one!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Expected unique result or null, but got more than one! (through reference chain: org.springframework.data.mongodb.core.aggregation.AggregationResults[\"uniqueMappedResult\"]
.. 虽然我没有调用任何 getUniqueMappedResult 方法。
我做错了什么?
其次,我怎样才能摆脱我的输出 class TopTopic,而不是 return 使用 todaysCommentsCount 扩展的原始主题值,而不创建特殊输出 class?
感谢您的帮助。
第一部分
您正在将 AggregationResults
发送回调用者,其中 jackson 正在序列化为 json 并且在调用 getUniqueMappedResult
.
将主题字段 _id 添加到 TopTopic
并读取 AggregationResults
中的映射结果。
List<TopTopic> topTopics = result.getMappedResults()
你的输出看起来像
[
{
"_id": "5dbdca8112a617031728c417",
"todaysCommentsCount": 3
}
]
第二部分
您可以使用 $$ROOT
变量和 $first
在 $group
阶段映射整个文档,然后 $replaceRoot
提升合并文档( $mergeObjects
合并 doc
和 todaysCommentCount
).
将 todaysCommentsCount
添加到主题 class。
类似
MatchOperation filterCreatedDate = match(Criteria.where("createdDate").gte(today.atStartOfDay()).lt(today.plusDays(1).atStartOfDay()));
GroupOperation groupByTopic = group("topic").first("$$ROOT").as("doc").count().as("todaysCommentsCount");
SortOperation sortByTodaysCommentsCount = sort(Sort.Direction.DESC, "todaysCommentsCount");
ReplaceRootOperation replaceRoot = Aggregation.replaceRoot().withValueOf(ObjectOperators.valueOf("doc").mergeWith(new Document("todaysCommentsCount", "$todaysCommentsCount")));
Aggregation aggregation = newAggregation(filterCreatedDate, groupByTopic, sortByTodaysCommentsCount, replaceRoot);
AggregationResults<TopTopic> result = mongoTemplate.aggregate(aggregation, Comment.class, Topic.class);
List<Topic> topTopics = result.getMappedResults();
你的输出看起来像
[
{
"_id": "5dbdca8112a617031728c417",
"title" : "topic",
"todaysCommentsCount": 3
}
]
更新(添加 $lookup 阶段以拉入主题字段)
MatchOperation filterCreatedDate = match(Criteria.where("createdDate").gte(today.atStartOfDay()).lt(today.plusDays(1).atStartOfDay()));
GroupOperation groupByTopic = group("topic").count().as("todaysCommentsCount");
SortOperation sortByTodaysCommentsCount = sort(Sort.Direction.DESC, "todaysCommentsCount");
LookupOperation lookupOperation = LookupOperation.newLookup().
from("topic").
localField("_id").
foreignField("_id").
as("topic");
ReplaceRootOperation replaceRoot = Aggregation.replaceRoot().withValueOf(ObjectOperators.valueOf("todaysCommentsCount").mergeWithValuesOf(ArrayOperators.arrayOf("topic").elementAt(0)));
Aggregation aggregation = newAggregation(filterCreatedDate, groupByTopic, sortByTodaysCommentsCount, lookupOperation, replaceRoot);
AggregationResults<TopTopic> result = mongoTemplate.aggregate(aggregation, Comment.class, Topic.class);
List<Topic> topTopics = result.getMappedResults();