Spring Data MongoDB查询指南

2023/05/18

1. 概述

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

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

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

2. 文档查询

使用Spring Data查询MongoDB的一种更常见的方法是使用Query和Criteria类,它们非常接近原生运算符。

2.1 Is

这只是一个使用相等的标准。让我们看看它是如何工作的。

在以下示例中,我们将查找名为Eric的用户。

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

[
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581907"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Eric",
        "age" : 45
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581908"),
        "_class" : "cn.tuyucheng.taketoday.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" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Eric",
    "age" : 45
}

2.2 Regex

一种更灵活、更强大的查询类型是正则表达式。这将使用MongoDB$regex创建一个条件,该条件返回匹配该字段的正则表达式的所有记录。

它的工作方式类似于startingWith和endingWith操作。

在此示例中,我们将查找名称以A开头的所有用户。

以下是数据库的状态:

[
    {
        "_id": ObjectId("55c0e5e5511f0a164a581907"),
        "_class": "cn.tuyucheng.taketoday.model.User",
        "name": "Eric",
        "age": 45
    },
    {
        "_id": ObjectId("55c0e5e5511f0a164a581908"),
        "_class": "cn.tuyucheng.taketoday.model.User",
        "name": "Antony",
        "age": 33
    },
    {
        "_id": ObjectId("55c0e5e5511f0a164a581909"),
        "_class": "cn.tuyucheng.taketoday.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" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Antony",
        "age" : 33
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581909"),
        "_class" : "cn.tuyucheng.taketoday.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" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Eric",
    "age" : 45
}

2.3 Lt和gt

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

让我们举一个简单的例子,寻找20到50岁之间的所有用户。

数据库是:

[
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581907"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Eric",
        "age" : 45
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581908"),
        "_class" : "cn.tuyucheng.taketoday.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" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Eric",
    "age" : 45
}

2.4 Sort

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

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

首先,这是现有数据:

[
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581907"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Eric",
        "age" : 45
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581908"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Antony",
        "age" : 33
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581909"),
        "_class" : "cn.tuyucheng.taketoday.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" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Antony",
        "age" : 33
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581909"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Alice",
        "age" : 35
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581907"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Eric",
        "age" : 45
    }
]

2.5 Pageable

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

这是数据库的状态:

[
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581907"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Eric",
        "age" : 45
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581908"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Antony",
        "age" : 33
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581909"),
        "_class" : "cn.tuyucheng.taketoday.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" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Eric",
        "age" : 45
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581908"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Antony",
        "age" : 33
    }
]

3. 生成的查询方法

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

要利用这些类型的查询,我们唯一需要做的就是在Repository接口中声明方法:

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 StartingWith和endingWith

在2.2节中,我们探讨了一个基于正则表达式的查询。StartingWith和endingWith当然没有那么强大,但仍然非常有用,特别是如果我们不必实际实现它们。

这是操作的外观的简单示例:

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

当然,实际使用它的例子非常简单:

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

结果完全一样。

3.3 Between

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

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

调用该方法与之前返回完全相同的文档:

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

3.4 Like和OrderBy

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

我们将查找名称中包含字母A的所有用户,我们还将按年龄升序对结果进行排序:

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

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

[
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581908"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Antony",
        "age" : 33
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581909"),
        "_class" : "cn.tuyucheng.taketoday.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 $regex

我们还可以查看正则表达式驱动的查询,它当然会产生与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依赖项

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

<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-classes

QueryDSL使用Q-classes来创建查询,但由于我们真的不想手动创建这些,所以我们需要以某种方式生成它们

我们将使用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生命周期的process目标(或该目标之后的任何其他目标)之后,apt插件将在target/generated-sources/java/{your package structure}下生成新类

/**
 * 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 install工作正常并且生成了QUser类,但插件在pom中高亮显示。

快速解决方法是手动指向eclipse.ini中的JDK:

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

5.3 新Repository

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

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

5.4 Eq

启用支持后,现在让我们实现与之前说明的查询相同的查询。

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

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

5.5 StartingWith和EndingWith

同样,让我们实现前面的查询并查找名称以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 Between

下一个查询将返回年龄在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上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章