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

Руководство по запросам в Spring Data MongoDB

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

Задача: Медиана двух отсортированных массивов

Даны два отсортированных массива размерами n и m. Найдите медиану слияния этих двух массивов.
Временная сложность решения должна быть O(log(m + n)) ...

ANDROMEDA

1. Обзор

В этом руководстве основное внимание будет уделено созданию различных типов запросов в Spring Data MongoDB .

Мы рассмотрим запросы к документам с помощью классов Query и Criteria , автоматически сгенерированных методов запросов, запросов JSON и QueryDSL.

Чтобы узнать о настройке Maven, ознакомьтесь с нашей вводной статьей .

2. Запрос документов

Одним из наиболее распространенных способов запроса MongoDB с помощью Spring Data является использование классов Query и Criteria , которые очень точно отражают собственные операторы.

2.1. Является

Это просто критерий, использующий равенство. Давайте посмотрим, как это работает.

В следующем примере мы будем искать пользователей с именем Эрик .

Давайте посмотрим на нашу базу данных:

[
{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581908"),
"_class" : "org.foreach.model.User",
"name" : "Antony",
"age" : 55
}
}

Теперь давайте посмотрим на код запроса:

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Eric"));
List<User> users = mongoTemplate.find(query, User.class);

Как и ожидалось, эта логика возвращает:

{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
}

2.2. регулярное выражение

Более гибким и мощным типом запроса является регулярное выражение. Это создает критерий с использованием регулярного выражения MongoDB $regex , который возвращает все записи, подходящие для регулярного выражения для этого поля.

Он работает аналогично операциям startWith и endingWith .

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

Вот состояние базы:

[
{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581908"),
"_class" : "org.foreach.model.User",
"name" : "Antony",
"age" : 33
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581909"),
"_class" : "org.foreach.model.User",
"name" : "Alice",
"age" : 35
}
]

Теперь создадим запрос:

Query query = new Query();
query.addCriteria(Criteria.where("name").regex("^A"));
List<User> users = mongoTemplate.find(query,User.class);

Это запускается и возвращает 2 записи:

[
{
"_id" : ObjectId("55c0e5e5511f0a164a581908"),
"_class" : "org.foreach.model.User",
"name" : "Antony",
"age" : 33
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581909"),
"_class" : "org.foreach.model.User",
"name" : "Alice",
"age" : 35
}
]

Вот еще один быстрый пример, на этот раз ищем всех пользователей, имена которых заканчиваются на c :

Query query = new Query();
query.addCriteria(Criteria.where("name").regex("c$"));
List<User> users = mongoTemplate.find(query, User.class);

Итак, результат будет:

{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
}

2.3. л и гт

Эти операторы создают критерий с помощью операторов $lt (меньше) и $gt (больше).

Давайте возьмем быстрый пример, когда мы ищем всех пользователей в возрасте от 20 до 50 лет.

База данных:

[
{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581908"),
"_class" : "org.foreach.model.User",
"name" : "Antony",
"age" : 55
}
}

Код запроса:

Query query = new Query();
query.addCriteria(Criteria.where("age").lt(50).gt(20));
List<User> users = mongoTemplate.find(query,User.class);

И результаты для всех пользователей возрастом от 20 до 50 лет:

{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
}

2.4. Сортировать

Сортировка используется для указания порядка сортировки результатов.

В приведенном ниже примере возвращаются все пользователи, отсортированные по возрасту в порядке возрастания.

Во-первых, вот существующие данные:

[
{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581908"),
"_class" : "org.foreach.model.User",
"name" : "Antony",
"age" : 33
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581909"),
"_class" : "org.foreach.model.User",
"name" : "Alice",
"age" : 35
}
]

После выполнения сортировки :

Query query = new Query();
query.with(Sort.by(Sort.Direction.ASC, "age"));
List<User> users = mongoTemplate.find(query,User.class);

А вот результат запроса, красиво отсортированный по возрасту :

[
{
"_id" : ObjectId("55c0e5e5511f0a164a581908"),
"_class" : "org.foreach.model.User",
"name" : "Antony",
"age" : 33
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581909"),
"_class" : "org.foreach.model.User",
"name" : "Alice",
"age" : 35
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
}
]

2.5. Постраничный

Давайте посмотрим на быстрый пример использования пагинации.

Вот состояние базы:

[
{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581908"),
"_class" : "org.foreach.model.User",
"name" : "Antony",
"age" : 33
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581909"),
"_class" : "org.foreach.model.User",
"name" : "Alice",
"age" : 35
}
]

Теперь вот логика запроса, просто запрос страницы размера 2:

final Pageable pageableRequest = PageRequest.of(0, 2);
Query query = new Query();
query.with(pageableRequest);

И результат, 2 документа, как и ожидалось:

[
{
"_id" : ObjectId("55c0e5e5511f0a164a581907"),
"_class" : "org.foreach.model.User",
"name" : "Eric",
"age" : 45
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581908"),
"_class" : "org.foreach.model.User",
"name" : "Antony",
"age" : 33
}
]

3. Сгенерированные методы запроса

Теперь давайте рассмотрим более распространенный тип запроса, который обычно предоставляет Spring Data, автоматически генерируемые запросы из имен методов.

Единственное, что нам нужно сделать, чтобы использовать такие запросы, — это объявить метод в интерфейсе репозитория:

public interface UserRepository 
extends MongoRepository<User, String>, QueryDslPredicateExecutor<User> {
...
}

3.1. FindByX

Мы начнем с простого, изучив запрос типа findBy. В этом случае мы будем использовать поиск по имени: ``

List<User> findByName(String name);

Как и в предыдущем разделе 2.1, запрос будет иметь те же результаты, что и все пользователи с заданным именем:

List<User> users = userRepository.findByName("Eric");

3.2. Начиная с и заканчивая с

В разделе 2.2 мы рассмотрели запрос на основе регулярных выражений . Начало и конец с, конечно, менее эффективны, но, тем не менее, весьма полезны, особенно если нам не нужно их реализовывать.

Вот краткий пример того, как будут выглядеть операции: ``

List<User> findByNameStartingWith(String regexp);
List<User> findByNameEndingWith(String regexp);

Пример фактического использования этого, конечно, будет очень простым:

List<User> users = userRepository.findByNameStartingWith("A");
List<User> users = userRepository.findByNameEndingWith("c");

И результаты точно такие же.

3.3. Между

Как и в разделе 2.3, это вернет всех пользователей с возрастом от ageGT до ageLT:

List<User> findByAgeBetween(int ageGT, int ageLT);

Вызов метода приведет к тому, что будут найдены точно такие же документы:

List<User> users = userRepository.findByAgeBetween(20, 50);

3.4. Нравится и заказать

На этот раз давайте рассмотрим более сложный пример, объединяющий два типа модификаторов для сгенерированного запроса.

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

List<User> users = userRepository.findByNameLikeOrderByAgeAsc("A");

Для базы данных, которую мы использовали в разделе 2.4, результатом будет:

[
{
"_id" : ObjectId("55c0e5e5511f0a164a581908"),
"_class" : "org.foreach.model.User",
"name" : "Antony",
"age" : 33
},
{
"_id" : ObjectId("55c0e5e5511f0a164a581909"),
"_class" : "org.foreach.model.User",
"name" : "Alice",
"age" : 35
}
]

4. Методы запросов JSON

Если мы не можем представить запрос с помощью имени метода или критериев, мы можем сделать что-то более низкоуровневое, используя аннотацию @Query .

С помощью этой аннотации мы можем указать необработанный запрос в виде строки запроса Mongo JSON.

4.1. FindBy

Давайте начнем с простого и сначала посмотрим, как мы будем представлять поиск по типу метода :

@Query("{ 'name' : ?0 }")
List<User> findUsersByName(String name);

Этот метод должен возвращать пользователей по имени. Заполнитель ?0 ссылается на первый параметр метода.

List<User> users = userRepository.findUsersByName("Eric");

4.2. $регулярное выражение

Мы также можем посмотреть на запрос, управляемый регулярным выражением, который, конечно же, дает тот же результат, что и в разделах 2.2 и 3.2:

@Query("{ 'name' : { $regex: ?0 } }")
List<User> findUsersByRegexpName(String regexp);

Использование также точно такое же:

List<User> users = userRepository.findUsersByRegexpName("^A");
List<User> users = userRepository.findUsersByRegexpName("c$");

4.3. $lt и $gt

Теперь давайте реализуем запрос lt и gt :

@Query("{ 'age' : { $gt: ?0, $lt: ?1 } }")
List<User> findUsersByAgeBetween(int ageGT, int ageLT);

Теперь, когда метод имеет 2 параметра, мы ссылаемся на каждый из них по индексу в необработанном запросе, ?0 и ?1:

List<User> users = userRepository.findUsersByAgeBetween(20, 50);

5. Запросы QueryDSL

MongoRepository имеет хорошую поддержку проекта QueryDSL , поэтому мы также можем использовать этот приятный, типобезопасный API и здесь.

5.1. Зависимости Maven

Во-первых, давайте удостоверимся, что у нас есть правильные зависимости Maven, определенные в pom:

<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-mongodb</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>4.3.1</version>
</dependency>

5.2. Q -классы

QueryDSL использует Q-классы для создания запросов, но поскольку мы не хотим создавать их вручную, нам нужно как-то их генерировать .

Мы собираемся использовать apt-maven-plugin для этого:

<plugin>    
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>
org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor
</processor>
</configuration>
</execution>
</executions>
</plugin>

Давайте посмотрим на класс User , уделив особое внимание аннотации @QueryEntity :

@QueryEntity 
@Document
public class User {

@Id
private String id;
private String name;
private Integer age;

// standard getters and setters
}

После запуска цели процесса жизненного цикла Maven (или любой другой цели после этой) плагин apt сгенерирует новые классы в target/generated-sources/java/{ваша структура пакета} :

/**
* QUser is a Querydsl query type for User
*/
@Generated("com.mysema.query.codegen.EntitySerializer")
public class QUser extends EntityPathBase<User> {

private static final long serialVersionUID = ...;

public static final QUser user = new QUser("user");

public final NumberPath<Integer> age = createNumber("age", Integer.class);

public final StringPath id = createString("id");

public final StringPath name = createString("name");

public QUser(String variable) {
super(User.class, forVariable(variable));
}

public QUser(Path<? extends User> path) {
super(path.getType(), path.getMetadata());
}

public QUser(PathMetadata<?> metadata) {
super(User.class, metadata);
}
}

Именно из-за этого класса нам не нужно создавать наши запросы.

В качестве примечания, если мы используем Eclipse, введение этого плагина вызовет следующее предупреждение в pom:

Вам нужно запустить сборку с JDK или иметь tools.jar в пути к классам. Если это происходит во время сборки eclipse, убедитесь, что вы также запускаете eclipse под JDK (com.mysema.maven:apt-maven-plugin:1.1.3:process:default:generate-sources

Установка Maven работает нормально, и создается класс QUser , но плагин выделен в файле pom.

Быстрое решение — вручную указать JDK в eclipse.ini :

...
-vm
{path_to_jdk}\jdk{your_version}\bin\javaw.exe

5.3. Новый репозиторий

Теперь нам нужно фактически включить поддержку QueryDSL в наших репозиториях, что делается простым расширением интерфейса QueryDslPredicateExecutor :

public interface UserRepository extends 
MongoRepository<User, String>, QuerydslPredicateExecutor<User>

5.4. уравнение

Теперь, когда поддержка включена, давайте реализуем те же запросы , которые мы проиллюстрировали ранее.

Начнем с простого равенства:

QUser qUser = new QUser("user");
Predicate predicate = qUser.name.eq("Eric");
List<User> users = (List<User>) userRepository.findAll(predicate);

5.5. Начиная с и заканчивая с

Точно так же давайте реализуем предыдущие запросы и найдем пользователей с именами, начинающимися с A :

QUser qUser = new QUser("user");
Predicate predicate = qUser.name.startsWith("A");
List<User> users = (List<User>) userRepository.findAll(predicate);

А также заканчивая c :

QUser qUser = new QUser("user");
Predicate predicate = qUser.name.endsWith("c");
List<User> users = (List<User>) userRepository.findAll(predicate);

Результат тот же, что и в разделах 2.2, 3.2 и 4.2.

5.6. Между

Следующий запрос вернет пользователей в возрасте от 20 до 50 лет, как и в предыдущих разделах:

QUser qUser = new QUser("user");
Predicate predicate = qUser.age.between(20, 50);
List<User> users = (List<User>) userRepository.findAll(predicate);

6. Заключение

В этой статье мы рассмотрели множество способов выполнения запросов с помощью Spring Data MongoDB.

Интересно сделать шаг назад и увидеть все мощные способы запросов к MongoDB, которые варьируются от ограниченного контроля до полного контроля с необработанными запросами.

Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub .