Перейти к основному содержимому

Spring Data MongoDB: прогнозы и агрегации

· 6 мин. чтения

1. Обзор

Spring Data MongoDB предоставляет простые высокоуровневые абстракции для собственного языка запросов MongoDB. В этой статье мы рассмотрим поддержку фреймворка Projections and Aggregation.

Если вы новичок в этой теме, обратитесь к нашей вводной статье Introduction to Spring Data MongoDB .

2. Проекция

В MongoDB проекции — это способ получить из базы данных только обязательные поля документа. Это уменьшает объем данных, которые необходимо передать с сервера базы данных на клиент, и, следовательно, повышает производительность.

С Spring Data MongDB проекции можно использовать как с MongoTemplate , так и с MongoRepository.

Прежде чем двигаться дальше, давайте посмотрим на модель данных, которую мы будем использовать:

@Document
public class User {
@Id
private String id;
private String name;
private Integer age;

// standard getters and setters
}

2.1. Проекции с использованием MongoTemplate

Методы include() и exclude() класса Field используются для включения и исключения полей соответственно :

Query query = new Query();
query.fields().include("name").exclude("id");
List<User> john = mongoTemplate.find(query, User.class);

Эти методы можно объединить в цепочку, чтобы включить или исключить несколько полей. Поле, помеченное как @Id ( _id в базе данных), всегда извлекается, если только оно не исключено явно.

Исключенные поля являются нулевыми в экземпляре класса модели, когда записи извлекаются с проекцией. В случае, когда поля относятся к типу-примитиву или их классу-оболочке, значения исключенных полей являются значениями по умолчанию для типов-примитивов.

Например, String будет нулевым , int / Integer будет 0 , а boolean / Boolean будет false .

Таким образом, в приведенном выше примере поле имени будет John , id будет нулевым , а age будет равным 0.

2.2. Прогнозы с использованием MongoRepository

При использовании MongoRepositories поля аннотации @Query могут быть определены в формате JSON: ``

@Query(value="{}", fields="{name : 1, _id : 0}")
List<User> findNameAndExcludeId();

Результат будет таким же, как при использовании шаблона MongoTemplate. Значение = "{}" означает, что фильтры отсутствуют, и, следовательно, будут выбраны все документы.

3. Агрегация

Агрегация в MongoDB была создана для обработки данных и возврата вычисленных результатов. Данные обрабатываются поэтапно, и выходные данные одного этапа предоставляются в качестве входных данных для следующего этапа. Эта возможность применять преобразования и выполнять вычисления с данными поэтапно делает агрегирование очень мощным инструментом для аналитики.

Spring Data MongoDB предоставляет абстракцию для собственных запросов агрегации с использованием трех классов: Aggregation , который оборачивает запрос агрегации, AggregationOperation , который оборачивает отдельные этапы конвейера, и AggregationResults , который является контейнером результата, полученного в результате агрегирования.

Для выполнения и агрегации сначала создайте конвейеры агрегации с помощью методов статического компоновщика в классе Aggregation , затем создайте экземпляр Aggregation с помощью метода newAggregation() в классе Aggregation и, наконец, запустите агрегацию с помощью MongoTemplate :

MatchOperation matchStage = Aggregation.match(new Criteria("foo").is("bar"));
ProjectionOperation projectStage = Aggregation.project("foo", "bar.baz");

Aggregation aggregation
= Aggregation.newAggregation(matchStage, projectStage);

AggregationResults<OutType> output
= mongoTemplate.aggregate(aggregation, "foobar", OutType.class);

Обратите внимание, что и MatchOperation , и ProjectionOperation реализуют AggregationOperation . Аналогичные реализации существуют и для других конвейеров агрегации. OutType — это модель данных для ожидаемого результата.

Теперь мы рассмотрим несколько примеров и их пояснения, чтобы охватить основные конвейеры и операторы агрегации.

Набор данных, который мы будем использовать в этой статье, содержит сведения обо всех почтовых индексах в США, которые можно загрузить из репозитория MongoDB .

Давайте посмотрим на образец документа после его импорта в коллекцию с именем zip в тестовой базе данных.

{
"_id" : "01001",
"city" : "AGAWAM",
"loc" : [
-72.622739,
42.070206
],
"pop" : 15338,
"state" : "MA"
}

Для простоты и краткости кода в следующих фрагментах кода мы будем предполагать, что все статические методы класса Aggregation импортированы статически.

3.1. Получить все штаты с населением более 10 миллионов человек в порядке убывания населения

Здесь у нас будет три пайплайна:

  1. $group этап суммирования населения всех почтовых индексов
  2. Этап $match для фильтрации штатов с населением более 10 миллионов человек.
  3. Этап $sort для сортировки всех документов в порядке убывания численности.

Ожидаемый результат будет иметь поле _id в качестве состояния и поле statePop с общей численностью состояния. Создадим для этого модель данных и запустим агрегацию:

public class StatePoulation {

@Id
private String state;
private Integer statePop;

// standard getters and setters
}

Аннотация @Id будет отображать поле _id из вывода в состояние в модели:

GroupOperation groupByStateAndSumPop = group("state")
.sum("pop").as("statePop");
MatchOperation filterStates = match(new Criteria("statePop").gt(10000000));
SortOperation sortByPopDesc = sort(Sort.by(Direction.DESC, "statePop"));

Aggregation aggregation = newAggregation(
groupByStateAndSumPop, filterStates, sortByPopDesc);
AggregationResults<StatePopulation> result = mongoTemplate.aggregate(
aggregation, "zips", StatePopulation.class);

Класс AggregationResults реализует Iterable , поэтому мы можем перебирать его и печатать результаты.

Если модель выходных данных неизвестна, можно использовать стандартный класс Document MongoDB .

3.2. Получите наименьший штат по среднему населению города

Для этой задачи нам понадобятся четыре этапа:

  1. $group для суммирования общей численности населения каждого города
  2. $group для расчета средней численности населения каждого штата
  3. Этап $sort для упорядочения штатов по среднему населению города в порядке возрастания
  4. $limit , чтобы получить первый штат с наименьшим средним населением города

Хотя это не обязательно, мы будем использовать дополнительную стадию $project для переформатирования документа в соответствии с моделью данных StatePopulation .

GroupOperation sumTotalCityPop = group("state", "city")
.sum("pop").as("cityPop");
GroupOperation averageStatePop = group("_id.state")
.avg("cityPop").as("avgCityPop");
SortOperation sortByAvgPopAsc = sort(Sort.by(Direction.ASC, "avgCityPop"));
LimitOperation limitToOnlyFirstDoc = limit(1);
ProjectionOperation projectToMatchModel = project()
.andExpression("_id").as("state")
.andExpression("avgCityPop").as("statePop");

Aggregation aggregation = newAggregation(
sumTotalCityPop, averageStatePop, sortByAvgPopAsc,
limitToOnlyFirstDoc, projectToMatchModel);

AggregationResults<StatePopulation> result = mongoTemplate
.aggregate(aggregation, "zips", StatePopulation.class);
StatePopulation smallestState = result.getUniqueMappedResult();

В этом примере мы уже знаем, что в результате будет только один документ, так как мы ограничиваем количество выходных документов до 1 на последнем этапе. Таким образом, мы можем вызвать getUniqueMappedResult() , чтобы получить требуемый экземпляр StatePopulation .

Еще одна вещь, которую следует отметить, это то, что вместо того, чтобы полагаться на аннотацию @Id для сопоставления _id с состоянием, мы явно сделали это на этапе проецирования.

3.3. Получите состояние с максимальным и минимальным почтовым индексом

Для этого примера нам нужны три этапа:

  1. $group для подсчета количества почтовых индексов для каждого штата
  2. $ sort для упорядочения штатов по количеству почтовых индексов
  3. $group , чтобы найти штат с максимальным и минимальным почтовыми индексами, используя операторы $first и $last
GroupOperation sumZips = group("state").count().as("zipCount");
SortOperation sortByCount = sort(Direction.ASC, "zipCount");
GroupOperation groupFirstAndLast = group().first("_id").as("minZipState")
.first("zipCount").as("minZipCount").last("_id").as("maxZipState")
.last("zipCount").as("maxZipCount");

Aggregation aggregation = newAggregation(sumZips, sortByCount, groupFirstAndLast);

AggregationResults<Document> result = mongoTemplate
.aggregate(aggregation, "zips", Document.class);
Document document= result.getUniqueMappedResult();

Здесь мы не использовали какую-либо модель, а использовали документ , уже предоставленный с драйвером MongoDB.

4. Вывод

В этой статье мы узнали, как получить указанные поля документа в MongoDB, используя проекции в Spring Data MongoDB.

Мы также узнали о поддержке платформы агрегации MongoDB в Spring Data. Мы рассмотрели основные этапы агрегирования — группировка, проект, сортировка, ограничение и сопоставление, а также рассмотрели несколько примеров его практического применения. Полный исходный код доступен на GitHub .