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 获取。