1. 简介

本教程将探讨如何使用 Spring Data MongoDB 在 MongoDB 中创建多条件查询。我们将介绍多种实现方式,包括 Criteria 链式调用、@Query 注解和 QueryDSL,帮助你根据实际场景选择最合适的方案。

2. 项目搭建

首先需要在项目中添加必要的依赖。在 pom.xml 文件中引入 Spring Data MongoDB starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
    <version>3.3.1</version>
</dependency>

这个依赖为 Spring Boot 项目提供了完整的 MongoDB 数据访问支持。

2.1. 定义 MongoDB 文档和仓库

接下来定义一个 MongoDB 文档(使用 @Document 注解的 Java 类),它映射到 MongoDB 的集合。以 Product 文档为例:

@Document(collection = "products")
public class Product {
    @Id
    private String id;
    private String name;
    private String category;
    private double price;
    private boolean available;

    // Getters and setters
}

在 Spring Data MongoDB 中,我们可以通过注入 MongoTemplate 创建自定义仓库执行高级操作。MongoTemplate 提供了丰富的查询、聚合和 CRUD 操作方法:

@Repository
public class CustomProductRepositoryImpl implements CustomProductRepository {
    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public List find(Query query, Class entityClass) {
        return mongoTemplate.find(query, entityClass);
    }
}

2.2. MongoDB 示例数据

在开始编写查询前,假设 MongoDB 的 products 集合中有以下示例数据:

[
    {
        "name": "MacBook Pro M3",
        "price": 1500,
        "category": "Laptop",
        "available": true
    },
    {
        "name": "MacBook Air M2",
        "price": 1000,
        "category": "Laptop",
        "available": false
    },
    {
        "name": "iPhone 13",
        "price": 800,
        "category": "Phone",
        "available": true
    }
]

这些数据将用于后续查询测试。

3. 构建 MongoDB 查询

在 Spring Data MongoDB 中构建复杂查询时,主要使用 andOperator()orOperator() 方法组合多个条件。这些方法对创建多条件查询至关重要。

3.1. 使用 andOperator()

andOperator() 方法使用 AND 运算符组合多个条件。文档必须满足所有条件才能匹配查询,适合需要强制满足多个条件的场景。

使用 andOperator() 构建查询的示例:

List<Product> findProductsUsingAndOperator(String name, int minPrice, String category, boolean available) {
    Query query = new Query();
    query.addCriteria(new Criteria().andOperator(Criteria.where("name")
      .is(name), Criteria.where("price")
      .gt(minPrice), Criteria.where("category")
      .is(category), Criteria.where("available")
      .is(available)));
   return customProductRepository.find(query, Product.class);
}

假设要查询名称为 "MacBook Pro M3"、价格高于 $1000 且有库存的笔记本电脑:

List<Product> actualProducts = productService.findProductsUsingAndOperator("MacBook Pro M3", 1000, "Laptop", true);

assertThat(actualProducts).hasSize(1);
assertThat(actualProducts.get(0).getName()).isEqualTo("MacBook Pro M3");

3.2. 使用 orOperator()

orOperator() 方法使用 OR 运算符组合条件。文档只需满足任一条件即可匹配查询,适合查询满足多个条件中至少一个的场景。

使用 orOperator() 构建查询的示例:

List<Product> findProductsUsingOrOperator(String category, int minPrice) {
    Query query = new Query();
    query.addCriteria(new Criteria().orOperator(Criteria.where("category")
      .is(category), Criteria.where("price")
      .gt(minPrice)));

    return customProductRepository.find(query, Product.class);
}

查询属于 "Laptop" 类别或价格高于 $1000 的产品:

actualProducts = productService.findProductsUsingOrOperator("Laptop", 1000);
assertThat(actualProducts).hasSize(2);

3.3. 组合 andOperator()orOperator()

可以组合使用 andOperator()orOperator() 创建复杂查询:

List<Product> findProductsUsingAndOperatorAndOrOperator(String category1, int price1, String name1, boolean available1) {
    Query query = new Query();
    query.addCriteria(new Criteria().orOperator(
      new Criteria().andOperator(
        Criteria.where("category").is(category1),
        Criteria.where("price").gt(price1)),
      new Criteria().andOperator(
        Criteria.where("name").is(name1),
        Criteria.where("available").is(available1)
      )
    ));

    return customProductRepository.find(query, Product.class);
}

此方法使用 orOperator() 定义主要结构,内部包含两个 andOperator() 条件。例如查询属于 "Laptop" 类别且价格高于 $1000,或名称为 "MacBook Pro M3" 且有库存的产品:

actualProducts = productService.findProductsUsingAndOperatorAndOrOperator("Laptop", 1000, "MacBook Pro M3", true);

assertThat(actualProducts).hasSize(1);
assertThat(actualProducts.get(0).getName()).isEqualTo("MacBook Pro M3");

3.4. 使用链式方法

还可以利用 Criteria 类的链式方法构建查询,通过 and() 方法连接多个条件。这种方式清晰简洁,适合构建可读性强的复杂查询

List<Product> findProductsUsingChainMethod(String name1, int price1, String category1, boolean available1) {
    Criteria criteria = Criteria.where("name").is(name1)
      .and("price").gt(price1)
      .and("category").is(category1)
      .and("available").is(available1);
    return customProductRepository.find(new Query(criteria), Product.class);
}

调用此方法查询名称为 "MacBook Pro M3"、价格高于 $1000 且有库存的产品:

actualProducts = productService.findProductsUsingChainMethod("MacBook Pro M3", 1000, "Laptop", true);

assertThat(actualProducts).hasSize(1);
assertThat(actualProducts.get(0).getName()).isEqualTo("MacBook Pro M3");

4. 使用 @Query 注解处理多条件

除了使用 MongoTemplate 的自定义仓库,还可以创建继承 MongoRepository 的仓库接口,通过 @Query 注解定义多条件查询。这种方式可以直接在仓库中定义复杂查询,无需编程构建

ProductRepository 接口中定义自定义方法:

public interface ProductRepository extends MongoRepository<Product, String> {
    @Query("{ 'name': ?0, 'price': { $gt: ?1 }, 'category': ?2, 'available': ?3 }")
    List<Product> findProductsByNamePriceCategoryAndAvailability(String name, double minPrice, String category, boolean available);
    
    @Query("{ $or: [{ 'category': ?0, 'available': ?1 }, { 'price': { $gt: ?2 } } ] }")
    List<Product> findProductsByCategoryAndAvailabilityOrPrice(String category, boolean available, double minPrice);
}

第一个方法 findProductsByNamePriceCategoryAndAvailability() 检索满足所有指定条件的产品:

actualProducts = productRepository.findProductsByNamePriceCategoryAndAvailability("MacBook Pro M3", 1000, "Laptop",  true);

assertThat(actualProducts).hasSize(1);
assertThat(actualProducts.get(0).getName()).isEqualTo("MacBook Pro M3");

第二个方法 findProductsByCategoryAndAvailabilityOrPrice() 更灵活,查询属于特定类别且有库存,或价格高于指定最小值的产品:

actualProducts = productRepository.findProductsByCategoryAndAvailabilityOrPrice("Laptop", false, 600);

assertThat(actualProducts).hasSize(3);

5. 使用 QueryDSL

QueryDSL 是一个框架,允许以类型安全的方式编程构建查询。下面介绍在 Spring Data MongoDB 项目中设置和使用 QueryDSL 处理多条件查询。

5.1. 添加 QueryDSL 依赖

首先在 pom.xml 中添加 QueryDSL 依赖:

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-mongodb</artifactId>
    <version>5.1.0</version>
</dependency>

5.2. 生成 Q 类

QueryDSL 需要为领域对象生成辅助类(通常以 "Q" 前缀命名,如 QProduct)。使用 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>

构建时,注解处理器会为每个 MongoDB 文档生成 Q 类。例如 Product 类会生成 QProduct 类,提供类型安全的字段访问。

接下来修改仓库接口,继承 QuerydslPredicateExecutor

public interface ProductRepository extends MongoRepository<Product, String>, QuerydslPredicateExecutor<Product> {
}

5.3. 在 QueryDSL 中使用 AND

在 QueryDSL 中,使用 Predicate 接口(表示布尔表达式)构建复杂查询。and() 方法组合多个条件,确保文档满足所有指定条件:

List<Product> findProductsUsingQueryDSLWithAndCondition(String category, boolean available, String name, double minPrice) {
    QProduct qProduct = QProduct.product;
    Predicate predicate = qProduct.category.eq(category)
      .and(qProduct.available.eq(available))
      .and(qProduct.name.eq(name))
      .and(qProduct.price.gt(minPrice));

    return StreamSupport.stream(productRepository.findAll(predicate).spliterator(), false)
      .collect(Collectors.toList());
}

此方法创建 QProduct 实例,使用 and() 方法构建 Predicate,最后通过 productRepository.findAll(predicate) 执行查询:

actualProducts = productService.findProductsUsingQueryDSLWithAndCondition("Laptop", true, "MacBook Pro M3", 1000);

assertThat(actualProducts).hasSize(1);
assertThat(actualProducts.get(0).getName()).isEqualTo("MacBook Pro M3");

5.4. 在 QueryDSL 中使用 OR

使用 or() 方法构建 OR 条件查询。文档只需满足任一条件即可匹配查询

List<Product> findProductsUsingQueryDSLWithOrCondition(String category, String name, double minPrice) {
    QProduct qProduct = QProduct.product;
    Predicate predicate = qProduct.category.eq(category)
      .or(qProduct.name.eq(name))
      .or(qProduct.price.gt(minPrice));

    return StreamSupport.stream(productRepository.findAll(predicate).spliterator(), false)
      .collect(Collectors.toList());
}

or() 方法确保产品满足任一条件即匹配:

actualProducts = productService.findProductsUsingQueryDSLWithOrCondition("Laptop", "MacBook", 800);

assertThat(actualProducts).hasSize(2);

5.5. 在 QueryDSL 中组合 AND 和 OR

可以在同一个查询中组合 and()or() 方法。这种灵活性允许指定必须满足的条件和可选条件

List<Product> findProductsUsingQueryDSLWithAndOrCondition(String category, boolean available, String name, double minPrice) {
    QProduct qProduct = QProduct.product;
    Predicate predicate = qProduct.category.eq(category)
      .and(qProduct.available.eq(available))
      .or(qProduct.name.eq(name).and(qProduct.price.gt(minPrice)));

    return StreamSupport.stream(productRepository.findAll(predicate).spliterator(), false)
      .collect(Collectors.toList());
}

此方法组合 and()or() 条件,查询属于特定类别且有库存,或名称匹配且价格高于指定值的产品:

actualProducts = productService.findProductsUsingQueryDSLWithAndOrCondition("Laptop", true, "MacBook Pro M3", 1000);
assertThat(actualProducts).hasSize(3);

6. 总结

本文探讨了在 Spring Data MongoDB 中构建多条件查询的多种方法。对于简单查询,Criteria 或链式方法因其简洁性足够使用。但当查询涉及复杂逻辑和多条件嵌套时,推荐使用 @Query 注解或 QueryDSL,因为它们具有更好的可读性和可维护性。

简单场景:使用 Criteria 链式调用
复杂逻辑:使用 @Query 注解或 QueryDSL
⚠️ 性能注意:复杂嵌套查询可能影响性能,建议添加适当索引

示例源码可在 GitHub 获取。


原始标题:Multiple Criteria in Spring Data Mongo DB Query | Baeldung