MongoTemplate 中如何构建复杂的查询条件 (Criteria API)?
Spring Data MongoDB 提供了强大的 Criteria API,允许我们以编程方式构建复杂的 MongoDB 查询条件。
核心类是 org.springframework.data.mongodb.core.query.Criteria
和 org.springframework.data.mongodb.core.query.Query
。
基本步骤:
- 创建一个
Query
对象。 - 使用
Criteria.where("fieldName")
开始定义条件。 - 链接各种条件方法 (
is()
,gt()
,lt()
,regex()
,in()
,andOperator()
,orOperator()
等)。 - 将构建好的
Criteria
添加到Query
对象中 (query.addCriteria(criteria)
)。 - 使用
MongoTemplate
执行查询。
常用操作符和方法:
1. 比较操作符:
is(Object value)
: 等于 (field: value
)ne(Object value)
: 不等于 (field: { $ne: value }
)lt(Object value)
: 小于 (field: { $lt: value }
)lte(Object value)
: 小于等于 (field: { $lte: value }
)gt(Object value)
: 大于 (field: { $gt: value }
)gte(Object value)
: 大于等于 (field: { $gte: value }
)in(Object... values)
或in(Collection<?> values)
: 包含在数组中 (field: { $in: [value1, value2, ...] }
)nin(Object... values)
或nin(Collection<?> values)
: 不包含在数组中 (field: { $nin: [value1, value2, ...] }
)
示例:
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Criteria.where; // 静态导入更简洁// 假设有一个 User 类和 MongoTemplate 实例
// MongoTemplate mongoTemplate = ...;// 查询年龄为 30 的用户
Query queryAge30 = new Query(where("age").is(30));
List<User> usersAge30 = mongoTemplate.find(queryAge30, User.class);// 查询年龄大于 25 且小于 50 的用户
Query queryAgeRange = new Query(where("age").gt(25).lt(50));
// 等价于: Query queryAgeRange = new Query(new Criteria().andOperator(where("age").gt(25), where("age").lt(50)));
List<User> usersInRange = mongoTemplate.find(queryAgeRange, User.class);// 查询名字是 "Alice" 或 "Bob" 的用户
Query queryNames = new Query(where("name").in("Alice", "Bob"));
List<User> usersByNames = mongoTemplate.find(queryNames, User.class);
2. 逻辑操作符:
andOperator(Criteria... criteria)
: AND 条件 ($and: [{crit1}, {crit2}, ...]
)orOperator(Criteria... criteria)
: OR 条件 ($or: [{crit1}, {crit2}, ...]
)norOperator(Criteria... criteria)
: NOR 条件 ($nor: [{crit1}, {crit2}, ...]
)not()
: NOT 操作 (通常与其他操作符结合, 如where("age").not().gt(18)
)
示例:
// 查询 (状态为 "ACTIVE" AND 积分大于 100) OR (是 VIP 用户)
Criteria activeAndPoints = new Criteria().andOperator(where("status").is("ACTIVE"),where("points").gt(100)
);
Criteria isVip = where("isVip").is(true);Query complexQuery = new Query(new Criteria().orOperator(activeAndPoints, isVip));
List<User> complexUsers = mongoTemplate.find(complexQuery, User.class);// 查询不是管理员的用户
Query notAdminQuery = new Query(where("roles").not().is("ADMIN")); // 如果 roles 是单个值
// 如果 roles 是数组,且不想包含 ADMIN:
// Query notAdminQuery = new Query(where("roles").nin("ADMIN"));
// 或者更精确地:Query notAdminQuery = new Query(where("roles").not().elemMatch(where("$eq").is("ADMIN")));
注意关于 and
的 chaining:
当你在同一个 Criteria.where("field")
实例上链式调用多个条件时,它们默认是以 AND 关系应用于 该字段。
例如:where("age").gt(20).lt(30)
相当于 { "age": { "$gt": 20, "$lt": 30 } }
如果你需要对 不同字段 进行 AND 操作,可以链式调用 and("otherField")
,或者使用 andOperator
。
where("age").gt(20).and("name").is("Alice")
相当于 { "age": { "$gt": 20 }, "name": "Alice" }
通常,将多个独立的 Criteria
实例通过 query.addCriteria()
(隐式 AND)或显式的 andOperator
组合起来更清晰。
3. 元素操作符:
exists(boolean value)
: 字段是否存在 (field: { $exists: true/false }
)type(int bsonType)
或type(BsonType bsonType)
: 字段类型是否匹配 (field: { $type: BsonType }
)
示例:
import org.bson.BsonType;// 查询有 "email" 字段的用户
Query queryHasEmail = new Query(where("email").exists(true));// 查询 "age" 字段类型为数字 (INT32, INT64, DOUBLE) 的用户
Query queryAgeIsNumber = new Query(where("age").type(BsonType.INT32)); // 或者 BsonType.DOUBLE, BsonType.INT64
// 或者更通用地查询多种数字类型:
// Query queryAgeIsNumber = new Query(new Criteria().orOperator(
// where("age").type(BsonType.INT32),
// where("age").type(BsonType.INT64),
// where("age").type(BsonType.DOUBLE)
// ));List<User> usersWithEmail = mongoTemplate.find(queryHasEmail, User.class);
4. 数组操作符:
all(Object... values)
或all(Collection<?> values)
: 数组字段包含所有指定元素 (field: { $all: [value1, value2, ...] }
)elemMatch(Criteria criteria)
: 数组字段中至少有一个元素匹配指定的Criteria
(field: { $elemMatch: { criteria } }
)size(int s)
: 数组字段的长度等于 s (field: { $size: s }
)
示例:
// 假设 User 有一个 List<String> tags 字段// 查询标签同时包含 "java" 和 "mongodb" 的用户
Query queryTagsAll = new Query(where("tags").all("java", "mongodb"));// 查询订单列表中至少有一个订单金额大于 100 的用户
// 假设 User 有一个 List<Order> orders 字段,Order 有 amount 字段
Query queryOrderAmount = new Query(where("orders").elemMatch(where("amount").gt(100)));// 查询有 3 个标签的用户
Query queryTagsSize = new Query(where("tags").size(3));
5. 正则表达式操作符:
regex(String re)
: 匹配正则表达式 (field: /re/
)regex(String re, String options)
: 匹配正则表达式并指定选项 (如 “i” 忽略大小写) (field: { $regex: re, $options: 'i' }
)regex(Pattern pattern)
: 使用 JavaPattern
对象
示例:
// 查询名字以 "A" 开头的用户 (忽略大小写)
Query queryNameStartsWithA = new Query(where("name").regex("^A", "i"));
// 或者使用 Pattern
// Pattern pattern = Pattern.compile("^A", Pattern.CASE_INSENSITIVE);
// Query queryNameStartsWithA = new Query(where("name").regex(pattern));List<User> usersStartsWithA = mongoTemplate.find(queryNameStartsWithA, User.class);
6. 文本搜索 (Text Search):
需要集合中有文本索引 (@TextIndexed
注解或手动创建)。
- 使用
TextCriteria
和TextQuery
。
示例:
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.mongodb.core.query.TextQuery;// 假设 description 字段有文本索引
// 搜索描述中包含 "database" 或 "system" 的文档,并按相关性排序
TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matchingAny("database", "system");Query textSearchQuery = TextQuery.queryText(textCriteria).sortByScore(); // 按文本搜索得分排序List<Product> products = mongoTemplate.find(textSearchQuery, Product.class);
7. 地理空间查询 (Geospatial Queries):
需要地理空间索引。
within(Shape shape)
: 在指定形状内 (如$geoWithin
与$box
,$polygon
,$centerSphere
)near(Point point)
: 靠近某个点 ($near
)nearSphere(Point point)
: 在球面上靠近某个点 ($nearSphere
)maxDistance(double maxDistance)
: 与near
或nearSphere
结合使用,限制最大距离
示例:
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Box;// 假设 Venue 有一个 GeoJsonPoint location 字段,并有 2dsphere 索引// 查询坐标点 (longitude, latitude) 附近 10 公里内的地点
Point centerPoint = new Point(-73.9667, 40.78); // 经度, 纬度
Distance maxDistance = new Distance(10, Metrics.KILOMETERS);
Query nearQuery = new Query(where("location").nearSphere(centerPoint).maxDistance(maxDistance.getNormalizedValue()));
// maxDistance.getNormalizedValue() 将距离转换为弧度 (对于 $nearSphere)List<Venue> nearbyVenues = mongoTemplate.find(nearQuery, Venue.class);// 查询在指定矩形区域内的地点
Point lowerLeft = new Point(-74.0, 40.7);
Point upperRight = new Point(-73.9, 40.8);
Box boundingBox = new Box(lowerLeft, upperRight);
Query withinBoxQuery = new Query(where("location").within(boundingBox));List<Venue> venuesInBox = mongoTemplate.find(withinBoxQuery, Venue.class);
8. 嵌套文档查询:
使用点表示法 (.
):
// 假设 User 有一个内嵌的 Address 对象,Address 有 city 字段
// Address { String street; String city; String zipCode; }
// User { ..., Address address; ... }// 查询城市为 "New York" 的用户
Query queryCity = new Query(where("address.city").is("New York"));
List<User> usersInNewYork = mongoTemplate.find(queryCity, User.class);
组合多个 Criteria 到 Query 中:
默认情况下,如果你多次调用 query.addCriteria()
,它们之间是 AND 关系。
Query query = new Query();
query.addCriteria(where("status").is("ACTIVE")); // 条件1
query.addCriteria(where("age").gt(18)); // 条件2 (与条件1是AND关系)
// 等效于 { "status": "ACTIVE", "age": { "$gt": 18 } }
// 或 query.addCriteria(new Criteria().andOperator(where("status").is("ACTIVE"), where("age").gt(18)));
构建复杂查询的技巧:
- 静态导入:
import static org.springframework.data.mongodb.core.query.Criteria.where;
使代码更简洁。 - 分步构建: 将复杂的逻辑分解成多个小的
Criteria
对象,然后使用andOperator
或orOperator
组合它们。 - 可读性: 适当使用变量名来描述每个
Criteria
部分的含义。 - 链式调用: 充分利用链式API。
示例:一个更复杂的组合查询
查询所有 (年龄大于20且城市是"London") 或者 (是VIP客户且最近登录在过去7天内) 的用户。
import java.time.LocalDateTime;Criteria ageAndCity = new Criteria().andOperator(where("age").gt(20),where("address.city").is("London")
);Criteria vipAndRecentLogin = new Criteria().andOperator(where("isVip").is(true),where("lastLoginDate").gte(LocalDateTime.now().minusDays(7))
);Query finalQuery = new Query(new Criteria().orOperator(ageAndCity, vipAndRecentLogin));// 添加排序
// import org.springframework.data.domain.Sort;
// finalQuery.with(Sort.by(Sort.Direction.DESC, "registrationDate"));// 添加分页
// finalQuery.skip(0);
// finalQuery.limit(10);List<User> resultUsers = mongoTemplate.find(finalQuery, User.class);
Criteria API 为构建 MongoDB 查询提供了非常灵活和强大的方式,几乎可以表达 MongoDB 支持的所有查询操作。