在 Spring Data MongoDB 的 3.x 之前版本中,计数操作使用 MongoDB 的内部集合统计信息。随着 MongoDB 事务的引入,这不再可能,因为统计数据无法正确反映需要基于聚合的计数方法的事务期间的潜在变化。因此,在 2.x 版本中,如果没有事务正在执行,则 MongoOperations.count() 将使用集合统计信息。如果有事务正在执行,则 MongoOperations.count() 将使用聚合变量。
从 Spring Data MongoDB 3.x 开始,任何计数操作都会通过 MongoDB 的 countDocuments 使用基于聚合的计数方法,而不考虑过滤条件的存在。如果应用程序可以满足处理收集统计信息的限制,MongoOperations.estimatedCount() 提供了另一种选择。
提示:通过将 MongoTemplate#useEstimatedCount(...) 设置为 true,使用空过滤器查询的 MongoTemplate#count(...) 操作将被委托给 estimatedCount,只要没有活动的事务并且模板没有绑定到会话。通过 MongoTemplate#exactCount() 仍然可以获得准确的数字,但可能会加快速度。
注意:
MongoDB 的本地 countDocuments 方法和 $match 聚合,不支持 $near 和 $nearSphere,但需要 $geoWithin 和 $center 或 $centerSphere,不支持 $minDistance。
因此,一个给定的查询将使用 Reactive-/MongoTemplate 重写计数操作,以绕过这个问题,如下所示。
// 使用 $near 对源查询进行计数 { location : { $near : [-73.99171, 40.738868], $maxDistance : 1.1 } } // 现在使用 $geoWithin 和 $center 重写了查询 { location : { $geoWithin : { $center: [ [-73.99171, 40.738868], 1.1] } } } // 使用具有 $minDistance 和 $maxDistance 的 $near 对源查询进行计数 { location : { $near : [-73.99171, 40.738868], $minDistance : 0.1, $maxDistance : 1.1 } } // 重写的查询现在是 $nor $geowithin 的组合,用于解决不受支持的 $minDistance { $and :[ { $nor :[ { location :{ $geoWithin :{ $center :[ [-73.99171, 40.738868 ], 0.01] } } } ]}, { location :{ $geoWithin :{ $center :[ [-73.99171, 40.738868 ], 1.1] } } } ] }
在 Spring Data MongoDB 的 MongoTemplate 类中,定了如下几个 count() 方法,用来对文档进行计数,方法定义如下:
long count(Query query, Class<?> entityClass) 通过查询给定实体类的集合,返回给定查询的文档数
long count(Query query, Class<?> entityClass, String collectionName) 通过使用给定实体类查询给定集合来映射给定查询,返回给定查询的文档数
long count(Query query, String collectionName) 返回查询给定集合的给定查询的文档数
下面示例将演示 count 的用法,使用 count 来获取查询文档的个数,代码如下:
package com.hxstrive.springdata.mongodb.count; import com.hxstrive.springdata.mongodb.entity.Person; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; /** * 文档计数 * @author hxstrive.com */ @SpringBootTest public class CountDemo { @Autowired private MongoTemplate mongoTemplate; @BeforeEach public void init() { // 删除集合 mongoTemplate.dropCollection(Person.class); // 准备数据 mongoTemplate.insert(Person.builder().id(100).name("Tom").age(27).email("Tom@sina.com").build()); mongoTemplate.insert(Person.builder().id(200).name("Helen").age(30).email("Helen@outlook.com").build()); mongoTemplate.insert(Person.builder().id(300).name("Bill").age(47).email("bill@gmail.com").build()); mongoTemplate.insert(Person.builder().id(400).name("Joe").age(20).email("joe@163.com").build()); } @Test public void count() { // Executing count: { "age" : { "$gt" : 25}} in collection: person Criteria criteria = Criteria.where("age").gt(25); Query query = new Query(criteria); long count = mongoTemplate.count(query, Person.class); System.out.println("count=" + count); // 结果: // count=3 } @Test public void count2() { // Executing count: { "age" : { "$gt" : 25}} in collection: person Criteria criteria = Criteria.where("age").gt(25); Query query = new Query(criteria); long count = mongoTemplate.count(query, mongoTemplate.getCollectionName(Person.class)); System.out.println("count=" + count); // 结果: // count=3 } @Test public void count3() { // Executing count: { "age" : { "$gt" : 25}} in collection: person Criteria criteria = Criteria.where("age").gt(25); Query query = new Query(criteria); long count = mongoTemplate.count(query, Person.class, mongoTemplate.getCollectionName(Person.class)); System.out.println("count=" + count); // 结果: // count=3 } }
其中,Person 实体类的代码如下:
package com.hxstrive.springdata.mongodb.entity; import lombok.Builder; import lombok.Data; import lombok.ToString; @Data @ToString @Builder public class Person { /** 用户ID */ private int id; /** 用户姓名 */ private String name; /** 年龄 */ private int age; /** 电子邮件 */ private String email; }