1. 概述

本文将重点介绍如何在Spring Data MongoDB中构建各种类型的查询语句,使用不同的方法:

  • 使用Query和Criteria类
  • 自动生成的查询方法
  • 利用@Query使用JSON查询
  • 使用QueryDSL

关于Maven依赖配置,请翻阅我们上一篇文章

2. 文档查询

在Spring Data使用QueryCriteria类来查询 MongoDB是最常用的方式之一,它非常接近MongoDB的原生操作符。

2.1. is - 等于查询

is是最简单的等于查询操作。

下面例子中,我们查询名为Eric的用户。

数据库中的数据::

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

2.2. regex 正则表达式

正则表达式是更灵活强大的查询类型。使用MongoDB $regex,返回所有匹配的记录。

下面我们查询名字以A开头的用户。

数据库中原始数据:

[
    {
        "_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
    }
]

查询代码:

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.baeldung.model.User",
        "name" : "Antony",
        "age" : 33
    },
    {
        "_id" : ObjectId("55c0e5e5511f0a164a581909"),
        "_class" : "org.baeldung.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.baeldung.model.User",
    "name" : "Eric",
    "age" : 45
}

2.3. lt、gt - 大于、小于查询

ltgt方法对应mongo的$lt(小于)和$gt(大于)操作符。

下面例子中,我们查询年龄介于20-50之间的所有用户。

数据库初始数据:

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

查询结果:

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

2.4. 排序

Sort用来指定排序方式。

下面例子中,将用户按年龄升序排序后返回。

初始数据:

[
    {
        "_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
    }
]

查询代码:

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.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. 分页查询

数据库中的数据:

[
    {
        "_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
    }
]

查询逻辑中,我们只请求pagesize为2的一页数据:

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

结果和预期一样,只返回2条记录:

[
    {
        "_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 支持根据方法名字自动生成查询语句。

我们只需要做的是,定义Repository接口,并遵循Spring Data规范声明查询方法名字就行。

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

3.1. FindByX

FindByX中的X替换为查询条件的字段名。例如,按name字段查询:

List<User> findByName(String name);

同2.1节,下面代码会查询名字为Eric的所有用户:

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

3.2. StartingWith 和 endingWith

2.2节中,我们基于正则表达式实现了查询以XXX开头以XXX结尾的功能。

下面是自动生成方法的命名规则:

List<User> findByNameStartingWith(String regexp);

List<User> findByNameEndingWith(String regexp);

用法非常简单:

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

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

结果和2.2节中的是一样的。

3.3. Between

和2.3节类似,查询年龄介于ageGTageLT之间的所有用户:

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

用法:

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

3.4. Like 和 OrderBy

让我们看下更高级的用法,组合2种类型的修饰符。

查找名字中包含字母A的用户,并将结果按年龄升序排列:

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

使用2.4节中的数据库,结果为:

[
    {
        "_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 查询方法

如果利用自动生成的方法或Criteria类不能满足你的需求,我们还可以通过@Query注解,直接编写原生的Mongo JSON查询语句。

4.1. FindBy

find by xxx 例子:

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

占位符?0引用方中的第一个参数。

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

4.2 $regex

正则表达式的用法,结果同2.23.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

实现大于、小于查询:

@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,因此这里我们也可以利用这款很棒,类型安全的库实现Mongo查询。

5.1. 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-class

QueryDSL依赖Q-class来创建查询(命名规则为Q+实体类名)。但不需要我们手动创建这个类,我们只需安装maven插件,它会自动帮我生成:

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

运行mvn process后,apt插件会自动在target/generated-sources/java/{your package structure}下生成Q-class:

/**
 * 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中生成以下警告:

You need to run build with JDK or have tools.jar on the classpath. If this occurs during eclipse build make sure you run eclipse under JDK as well (com.mysema.maven:apt-maven-plugin:1.1.3:process:default:generate-sources

快速解决办法是在eclipse.ini中手动指定JDK:

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

5.3. 新的 Repository

要获得QueryDSL的支持,需要将我们的Repositor继承自QueryDslPredicateExecutor接口:

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

5.4. eq 等于查询

现在使用QueryDSL来实现我们前面的查询:

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进行查询的多种方法。

文中的示例和代码片段可从GitHub上获取。