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 .