Spring Data MongoDB中的查询指南

A Guide to Queries in Spring Data MongoDB

1.概述

本文将重点介绍在Spring Data MongoDB中构建不同类型的查询。

我们将使用Query和Criteria类,自动生成的查询方法,JSON查询和QueryDSL来查询文档。

对于Maven设置,请查看我们的介绍性文章。

2.文件查询

使用Spring数据查询MongoDB的一种更常见的方法是利用Query和Criteria类-它们非常类似于本机运算符。

2.1。 是

这只是使用平等的标准-让我们看看它是如何工作的。

在以下示例中-我们正在寻找名为Eric的用户。

让我们看一下我们的数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581907"),
       "_class" :"org.baeldung.model.User",
       "name" :"Eric",
       "age" : 45
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581908"),
       "_class" :"org.baeldung.model.User",
       "name" :"Antony",
       "age" : 55
    }
}

现在让我们看一下查询代码:

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

此逻辑按预期返回:

1
2
3
4
5
6
{
   "_id" : ObjectId("55c0e5e5511f0a164a581907"),
   "_class" :"org.baeldung.model.User",
   "name" :"Eric",
   "age" : 45
}

2.2。 正则表达式

正则表达式是一种更为灵活和强大的查询类型。 此c 使用MongoDB $ regex创建条件,该条件返回适用于此字段的此正则表达式的所有记录。

它的工作方式类似于startingWith,endingWith操作–让我们看一个例子。

现在,我们正在寻找名称以A开头的所有用户。

这是数据库的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581907"),
       "_class" :"org.baeldung.model.User",
       "name" :"Eric",
       "age" : 45
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581908"),
       "_class" :"org.baeldung.model.User",
       "name" :"Antony",
       "age" : 33
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581909"),
       "_class" :"org.baeldung.model.User",
       "name" :"Alice",
       "age" : 35
    }
]

现在创建查询:

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

运行并返回2条记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581908"),
       "_class" :"org.baeldung.model.User",
       "name" :"Antony",
       "age" : 33
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581909"),
       "_class" :"org.baeldung.model.User",
       "name" :"Alice",
       "age" : 35
    }
]

这是另一个快速的示例,这次查找名称以c结尾的所有用户:

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

因此结果将是:

1
2
3
4
5
6
{
   "_id" : ObjectId("55c0e5e5511f0a164a581907"),
   "_class" :"org.baeldung.model.User",
   "name" :"Eric",
   "age" : 45
}

2.3。 Lt和GT

这些运算符使用$ lt(小于)运算符和$ gt(大于)运算符创建条件。

让我们快速看一个例子–我们正在寻找年龄在20至50岁之间的所有用户。

该数据库是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581907"),
       "_class" :"org.baeldung.model.User",
       "name" :"Eric",
       "age" : 45
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581908"),
       "_class" :"org.baeldung.model.User",
       "name" :"Antony",
       "age" : 55
    }
}

此查询代码:

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

结果–所有年龄大于20岁且小于50岁的用户:

1
2
3
4
5
6
{
   "_id" : ObjectId("55c0e5e5511f0a164a581907"),
   "_class" :"org.baeldung.model.User",
   "name" :"Eric",
   "age" : 45
}

2.4。 分类

排序用于指定结果的排序顺序。

以下示例返回按年龄升序排序的所有用户。

首先–这是现有数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581907"),
       "_class" :"org.baeldung.model.User",
       "name" :"Eric",
       "age" : 45
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581908"),
       "_class" :"org.baeldung.model.User",
       "name" :"Antony",
       "age" : 33
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581909"),
       "_class" :"org.baeldung.model.User",
       "name" :"Alice",
       "age" : 35
    }
]

执行排序后:

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

这是查询的结果-按年龄很好地排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581908"),
       "_class" :"org.baeldung.model.User",
       "name" :"Antony",
       "age" : 33
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581909"),
       "_class" :"org.baeldung.model.User",
       "name" :"Alice",
       "age" : 35
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581907"),
       "_class" :"org.baeldung.model.User",
       "name" :"Eric",
       "age" : 45
    }
]

2.5。 可分页

让我们看一个使用分页的简单示例。

这是数据库的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581907"),
       "_class" :"org.baeldung.model.User",
       "name" :"Eric",
       "age" : 45
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581908"),
       "_class" :"org.baeldung.model.User",
       "name" :"Antony",
       "age" : 33
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581909"),
       "_class" :"org.baeldung.model.User",
       "name" :"Alice",
       "age" : 35
    }
]

现在,查询逻辑只需查询一个大小为2的页面:

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

结果是预期的2个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581907"),
       "_class" :"org.baeldung.model.User",
       "name" :"Eric",
       "age" : 45
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581908"),
       "_class" :"org.baeldung.model.User",
       "name" :"Antony",
       "age" : 33
    }
]

3.生成的查询方法

现在,让我们探索Spring Data通常提供的更常见的查询类型-方法名称之外的自动生成的查询。

我们需要利用这些类型的查询的唯一事情是在存储库接口上声明该方法:

1
2
3
4
public interface UserRepository
  extends MongoRepository<User, String>, QueryDslPredicateExecutor<User> {
    ...
}

з.1。 芬比克斯

我们将通过探索查询的findBy类型开始简单的操作–在这种情况下,按名称查找:

1
List<User> findByName(String name);

与上一节(2.1)相同,查询将具有相同的结果,查找具有给定名称的所有用户:

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

3.2。 开始于和结束于。

在2.2中,我们探索了基于正则表达式的查询。 开头和结尾当然没有那么强大,但是却非常有用-特别是在我们不必实际实现它们的情况下。

这是一个简单的操作示例:

1
List<User> findByNameStartingWith(String regexp);

1
List<User> findByNameEndingWith(String regexp);

当然,实际使用此示例非常简单:

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

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

结果完全一样。

3.3。 之间

与2.3类似,这将返回年龄在ageGT和ageLT之间的所有用户:

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

调用该方法将导致找到完全相同的文档:

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

3.4。 点赞和订购

这次让我们看一个更高级的示例-结合两种类型的修饰符来生成查询。

我们将寻找名称包含字母A的所有用户,并且还将按年龄将结果按升序排序:

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

对于我们在2.4中使用的数据库–结果将是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581908"),
       "_class" :"org.baeldung.model.User",
       "name" :"Antony",
       "age" : 33
    },
    {
       "_id" : ObjectId("55c0e5e5511f0a164a581909"),
       "_class" :"org.baeldung.model.User",
       "name" :"Alice",
       "age" : 35
    }
]

4. JSON查询方法

如果我们无法借助方法名称或条件来表示查询,则可以做一些更底层的操作–使用@Query批注。

使用此批注,我们可以将原始查询指定为Mongo JSON查询字符串。

4.1。 查找

让我们从简单开始,看看我们如何首先通过方法类型表示查找结果:

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

此方法应按名称返回用户-占位符?0引用该方法的第一个参数。

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

4.2 $正则表达式

我们还要看一下正则表达式驱动的查询–当然,它产生的结果与2.2和3.2中的结果相同:

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

用法也完全相同:

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

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

4.3。 $ lt和$ gt

现在,让我们实现 lt 和gt查询:

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

现在,既然该方法具有2个参数,我们将如何在原始查询中通过索引引用每个参数:?0和?1。

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

5. QueryDSL查询

MongoRepository对QueryDSL项目提供了很好的支持-因此,我们在这里也可以利用这种不错的,类型安全的API。

5.1。 Maven的依赖

首先,让我们确保在pom中定义了正确的Maven依赖项:

1
2
3
4
5
6
7
8
9
10
<dependency>
    <groupId>com.mysema.querydsl</groupId>
    <artifactId>querydsl-mongodb</artifactId>
    <version>3.6.6</version>
</dependency>
<dependency>
    <groupId>com.mysema.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>3.6.6</version>
</dependency>

5.2。 Q类

QueryDSL使用Q类创建查询。 但是,由于我们真的不想手工创建它们,因此我们需要以某种方式生成它们。

我们将使用apt-maven-plugin来做到这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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批注:

1
2
3
4
5
6
7
8
9
10
11
@QueryEntity
@Document
public class User {
 
    @Id
    private String id;
    private String name;
    private Integer age;
 
    // standard getters and setters
}

在运行了Maven生命周期的流程目标(或之后的任何其他目标)之后,apt插件将在target / generate-sources / java / {your package structure}下生成新类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * 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构建过程中发生这种情况,请确保同时在JDK下运行Eclipse(com.mysema.maven:apt-maven-plugin:1.1.3:process:default:generate-sources

Maven安装工作正常,并且生成了QUser类,但在pom中突出显示了一个插件。

一个快速修复是手动指向eclipse.ini中的JDK:

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

5.3。 新资料库

现在,我们需要在我们的存储库中实际启用QueryDSL支持–这可以通过简单地扩展QueryDslPredicateExecutor接口来完成:

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

5.4。 情商

在启用支持的情况下,让我们现在实现与前面说明的查询相同的查询。

我们将从简单的相等开始:

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

5.5。 开始于和结束于

同样,让我们实现之前的查询-并找到名称以A开头的用户:

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

并以c结尾:

1
2
3
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岁之间的用户-与前面的部分类似:

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

六,结论

在本文中,我们探讨了使用Spring Data MongoDB进行查询的多种方法。

退后一步,看看我们有多少强大的查询MongoDB的方法很有趣-从有限的控制到完全的原始查询控制。

所有这些示例和代码段的实现都可以在GitHub项目中找到–这是一个基于Eclipse的项目,因此应该很容易直接导入和运行。