2. 用例搭建
我们的用例包含一个模型类、一个仓库接口和一个服务类。此外,我们还会创建测试类来验证功能。
2.1. 创建模型类
先创建模型类,基于汽车的几个属性:
@Document
public class Car {
private String name;
private String brand;
public Car(String brand) {
this.brand = brand;
}
// getters and setters
}
省略了ID属性,因为示例中不需要。添加了带brand参数的构造函数方便测试。
2.2. 定义仓库接口
定义空仓库接口:
public interface CarRepository extends MongoRepository<Car, String> {
}
虽然模型没声明ID,但MongoDB会生成默认唯一ID,仍可通过findById()
访问。
2.3. 定义服务类
服务类将通过多种方式使用Spring Data仓库接口:
@Service
public class CountCarService {
@Autowired
private CarRepository repo;
}
后续章节会基于这个类扩展示例。
2.4. 准备测试
所有测试都基于服务类,先做基础配置避免重复代码:
public class CountCarServiceIntegrationTest {
@Autowired
private CountCarService service;
Car car1 = new Car("B-A");
@Before
public void init() {
service.insertCar(car1);
service.insertCar(new Car("B-B"));
}
}
每次测试前执行初始化,确保测试环境一致。car1
定义在init()
外以便后续测试使用。
3. 使用 CrudRepository
MongoRepository
继承自CrudRepository
,提供基础功能包括count()
方法。
3.1. count()
方法
仓库无需额外方法,直接在服务中调用:
public long getCountWithCrudRepository() {
return repo.count();
}
测试验证:
@Test
public void givenAllDocs_whenCrudRepositoryCount_thenCountEqualsSize() {
List<Car> all = service.findCars();
long count = service.getCountWithCrudRepository();
assertEquals(count, all.size());
}
确保count()
结果与集合文档总数一致。关键点:计数操作比列出所有文档更高效——小集合差别不大,但大集合可能导致OutOfMemoryError
。踩坑提醒:千万别用拉取全集合的方式计数!
3.2. 用 Example 对象过滤
CrudRepository
的count()
有重载版本,接受Example
对象:
public long getCountWithExample(Car item) {
return repo.count(Example.of(item));
}
只需填充对象属性,Spring自动生成查询。测试验证:
@Test
public void givenFilteredDocs_whenExampleCount_thenCountEqualsSize() {
long all = service.findCars()
.stream()
.filter(car -> car.getBrand().equals(car1.getBrand()))
.count();
long count = service.getCountWithExample(car1);
assertEquals(count, all);
}
4. 使用 @Query 注解
通过@Query
注解实现计数:
@Query(value = "{}", count = true)
Long countWithAnnotation();
必须指定value
,否则Spring会尝试从方法名解析查询。空查询{}
匹配所有文档,设置count=true
启用计数投影。
测试验证:
@Test
public void givenAllDocs_whenQueryAnnotationCount_thenCountEqualsSize() {
List<Car> all = service.findCars();
long count = service.getCountWithQueryAnnotation();
assertEquals(count, all.size());
}
4.1. 按属性过滤
扩展示例,按brand
过滤:
@Query(value = "{brand: ?0}", count = true)
public long countBrand(String brand);
value
使用MongoDB风格查询,?0
占位符对应方法参数。调用countBrand("A")
相当于执行{brand: "A"}
查询。
5. 编写派生查询方法
派生查询方法是不带@Query
注解的仓库方法,Spring通过方法名自动解析查询。
Long countByBrand(String brand);
方法名自动生成查询:统计brand
属性匹配参数值的文档。
服务层调用:
public long getCountBrandWithQueryMethod(String brand) {
return repo.countByBrand(brand);
}
测试验证:
@Test
public void givenFilteredDocs_whenQueryMethodCountByBrand_thenCountEqualsSize() {
String filter = "B-A";
long all = service.findCars()
.stream()
.filter(car -> car.getBrand().equals(filter))
.count();
long count = service.getCountBrandWithQueryMethod(filter);
assertEquals(count, all);
}
适用场景:少量查询时简单高效,但查询过多时维护成本高。
6. 使用 Criteria 构建动态查询
需要更灵活查询时,用Criteria
配合Query
对象。执行查询需MongoTemplate
,可直接注入服务:
@Autowired
private MongoTemplate mongo;
创建动态计数方法:
public long getCountBrandWithCriteria(String brand) {
Query query = new Query();
query.addCriteria(Criteria.where("brand")
.is(brand));
return mongo.count(query, Car.class);
}
优势:完全控制投影生成,适合动态查询场景。
6.1. 用 Example 对象过滤
Criteria
也支持Example
对象:
public long getCountWithExampleCriteria(Car item) {
Query query = new Query();
query.addCriteria(Criteria.byExample(item));
return mongo.count(query, Car.class);
}
既保持属性过滤的简洁性,又支持动态查询构建。
7. 总结
本文探讨了Spring Data MongoDB中实现文档计数的多种方式:
- 基础计数:直接使用
CrudRepository.count()
- 注解查询:通过
@Query
注解控制计数逻辑 - 派生方法:利用方法名自动生成查询
- 动态查询:结合
Criteria
和MongoTemplate
实现灵活计数
核心要点:
- 避免通过拉取全集合计数(性能陷阱!)
- 简单场景用派生方法或
Example
对象 - 复杂查询优先考虑
Criteria
动态构建
所有示例代码均经过测试验证,完整源码可在GitHub获取。