Spring Data - MongoDB - Агрегирование, как проецировать все поля сгруппированного ссылочного документа - PullRequest
0 голосов
/ 06 ноября 2019

У меня 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;
}

Поэтому я фактически сохраняю ссылку на идентификатор темы в комментариях.

Это мой сценарий агрегирования: Списоктем, которые получили наибольшее количество комментариев сегодня . Итак, три вещи:

  • Получить все комментарии за сегодня (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);

Это специальный класс для этого вывода:

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.

Что мне делатьне так ли?

Во-вторых, как мне избавиться от выходного класса TopTopic и вместо этого вернуть исходные значения темы, расширенные с помощью todaysCommentsCount, без создания специального выходного класса?

Я ценю за любыепомощь.

1 Ответ

1 голос
/ 09 ноября 2019

Первая часть

Вы отправляете AggregationResults обратно вызывающей стороне, где Джексон выполняет сериализацию в JSON и не удается, когда он вызывает getUniqueMappedResult.

Добавитьполе темы _id в TopTopic и чтение сопоставленных результатов в AggregationResults.

List<TopTopic> topTopics = result.getMappedResults()

Ваш вывод будет выглядеть как

 [
    {
       "_id": "5dbdca8112a617031728c417",
       "todaysCommentsCount": 3
    }
 ]

Вторая часть

Вы можете использовать переменную $$ROOT с $first для отображения всего документа на этапе $group, за которым следует $replaceRoot для продвижения документа слияния ($mergeObjects для объединения doc и todaysCommentCount).

Добавьте todaysCommentsCount к классу темы.

Что-то вроде

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
    }
 ]

Обновление (Добавить этап поиска $ для извлечения в полях темы)

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();
...