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 миллионов человек в порядке убывания населения
Здесь у нас будет три пайплайна:
$group
этап суммирования населения всех почтовых индексовЭтап $match
для фильтрации штатов с населением более 10 миллионов человек.Этап $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. Получите наименьший штат по среднему населению города
Для этой задачи нам понадобятся четыре этапа:
$group
для суммирования общей численности населения каждого города$group
для расчета средней численности населения каждого штатаЭтап $sort
для упорядочения штатов по среднему населению города в порядке возрастания$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. Получите состояние с максимальным и минимальным почтовым индексом
Для этого примера нам нужны три этапа:
$group
для подсчета количества почтовых индексов для каждого штата$ sort
для упорядочения штатов по количеству почтовых индексов$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 .