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 对象过滤

CrudRepositorycount()有重载版本,接受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中实现文档计数的多种方式:

  1. 基础计数:直接使用CrudRepository.count()
  2. 注解查询:通过@Query注解控制计数逻辑
  3. 派生方法:利用方法名自动生成查询
  4. 动态查询:结合CriteriaMongoTemplate实现灵活计数

核心要点

  • 避免通过拉取全集合计数(性能陷阱!)
  • 简单场景用派生方法或Example对象
  • 复杂查询优先考虑Criteria动态构建

所有示例代码均经过测试验证,完整源码可在GitHub获取。


原始标题:Count Documents Using Spring Data MongoDB Repository