Count Documents Using Spring Data MongoDB Repository – 使用Spring Data MongoDB存储库计算文件数

最后修改: 2022年 7月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll see different ways to use Spring Data MongoDB to count documents in our collections. We’ll use all the tools available in MongoRepository.

在本教程中,我们将看到使用Spring Data MongoDB来计算我们集合中的文档的不同方法。我们将使用MongoRepository中提供的所有工具。

We’ll use annotations, query methods, and methods from the CrudRepository. Also, we’ll build a simple service to aggregate our different use cases.

我们将使用注释、查询方法和来自CrudRepository的方法。另外,我们将建立一个简单的服务来聚合我们不同的用例。

2. Use Case Setup

2.用例设置

Our use case consists of a model class, a repository, and a service class. Moreover, we’ll create a test class to help us make sure everything is working as intended.

我们的用例由一个模型类、一个资源库和一个服务类组成。此外,我们将创建一个测试类,以帮助我们确保一切都在按计划进行。

2.1. Creating Model

2.1.创建模型

We’ll start by creating our model class. It’ll be based on a few properties of a car:

我们将从创建我们的模型类开始。它将基于汽车的一些属性。

@Document
public class Car {
    private String name;

    private String brand;

    public Car(String brand) {
        this.brand = brand;
    }

    // getters and setters
}

We’re omitting an ID property as we’re not going to need it in our examples. Also, we’re adding a constructor which takes a brand property as a parameter to make testing easier.

我们省略了一个ID属性,因为我们的例子中不需要它。此外,我们还添加了一个构造函数,该函数需要一个brand属性作为参数,以方便测试。

2.2. Defining Repository

2.2.定义存储库

Let’s define our repository without any methods:

让我们在没有任何方法的情况下定义我们的资源库。

public interface CarRepository extends MongoRepository<Car, String> {
}

We’re considering a String ID, even though we’re not declaring an ID property in our model. This is because MongoDB creates a default unique ID that we can still access via findById() if we want.

尽管我们没有在模型中声明一个ID属性,但我们考虑的是一个字符串ID。这是因为MongoDB创建了一个默认的唯一ID,如果我们想的话,仍然可以通过findById()来访问。

2.3. Defining Service Class

2.3.定义服务类别

Our service will take advantage of the Spring Data Repository interface in different ways.

我们的服务将以不同的方式利用Spring Data Repository接口。

Let’s define it with a reference to our repository:

让我们用一个对我们的存储库的引用来定义它:

@Service
public class CountCarService {

    @Autowired
    private CarRepository repo;
}

We’ll build upon this class in the next sections, covering examples.

在接下来的章节中,我们将在这个类别的基础上,涵盖实例。

2.4. Preparing Tests

2.4.准备测试

All our tests will run upon our service class. We just need a little setup so we don’t end up with duplicated code:

我们所有的测试都将在我们的服务类上运行。我们只需要做一点设置,这样我们就不会出现重复的代码了。

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"));
    }
}

We’ll run this block before each test to simplify our test scenarios. Also, we’re defining car1 outside init() to make it accessible in later tests.

我们将在每个测试前运行这个块,以简化我们的测试方案。此外,我们在init()之外定义car1,以使它在以后的测试中可以访问。

3. Using CrudRepository

3.使用CrudRepository

When using MongoRepository, which extends CrudRepository, we have access to basic functionality, including a count() method.

当使用扩展了CrudRepositoryMongoRepository时,我们可以使用基本功能,包括count()方法。

3.1. count() Method

3.1.count()方法

So, in our first count example, without any methods in our repository, we can just call it in our service:

因此,在我们的第一个计数例子中,在我们的资源库中没有任何方法,我们可以直接在我们的服务中调用它。

public long getCountWithCrudRepository() {
    return repo.count();
}

And we can test it:

而且我们可以测试它。

@Test
public void givenAllDocs_whenCrudRepositoryCount_thenCountEqualsSize() {
    List<Car> all = service.findCars();

    long count = service.getCountWithCrudRepository();

    assertEquals(count, all.size());
}

Consequently, we ensure that count() outputs the same number as the size of the list of all documents in our collection.

因此,我们确保count()输出的数字与我们集合中所有文件列表的大小相同。

Most importantly, we have to remember that a count operation is more cost-effective than listing all documents. This is both in terms of performance and reduced code. It won’t make a difference with small collections, but with a big one we might end up with an OutOfMemoryError. In short, it’s not a good idea to count documents by listing the whole collection.

最重要的是,我们必须记住,计数操作比列出所有文档更有成本效益。这在性能和减少代码方面都是如此。对于小型集合来说,这不会有什么影响,但是对于大型集合来说,我们可能会出现OutOfMemoryError简而言之,通过列出整个集合来计算文档并不是一个好主意。

3.2. Filter Using Example Object

3.2.使用Example Object的过滤器

CrudRepository can also help if we want to count documents with a specific property value. The count() method has an overloaded version, which receives an Example object:

CrudRepository如果我们想计算具有特定属性值的文档,也可以提供帮助。count()方法有一个重载版本,它接收一个Example对象:

public long getCountWithExample(Car item) {
    return repo.count(Example.of(item));
}

As a result, this simplifies the task. Now we just fill an object with the properties we want to filter, and Spring will do the rest. Let’s cover it in our tests:

因此,这就简化了任务。现在我们只需在一个对象中填入我们想要过滤的属性,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. Using the @Query Annotation

4.使用@Query注解

Our next example will be based on the @Query annotation:

我们的下一个例子将基于@Query注解。

@Query(value = "{}", count = true)
Long countWithAnnotation();

We have to specify the value property, otherwise Spring will try creating a query from our method name. But, since we want to count all documents, we simply specify an empty query.

我们必须指定value属性,否则Spring将尝试从我们的方法名称中创建一个查询。但是,由于我们想计算所有的文档,我们只需指定一个空的查询。

Then, we specify that the result of this query should be a count projection by setting the count property to true.

然后,我们通过将count属性设置为true来指定该查询的结果应该是一个计数投影。

Let’s test it:

让我们来测试一下。

@Test
public void givenAllDocs_whenQueryAnnotationCount_thenCountEqualsSize() {
    List<Car> all = service.findCars();

    long count = service.getCountWithQueryAnnotation();

    assertEquals(count, all.size());
}

4.1. Filter on a Property

4.1.对一个属性进行过滤

We can expand on our example, filtering by brand. Let’s add a new method to our repository:

我们可以扩展我们的例子,通过品牌进行过滤。让我们给我们的资源库添加一个新的方法。

@Query(value = "{brand: ?0}", count = true)
public long countBrand(String brand);

In our query value, we specify our full MongoDB style query. The “?0” placeholder represents the first parameter of our method, which will be our query parameter value.

在我们的查询value中,我们指定了我们完整的MongoDB风格查询。占位符”?0“代表我们方法的第一个参数,这将是我们的query参数值。

MongoDB queries have a JSON structure where we specify field names along values we want to filter by. So, when we call countBrand(“A”), the query translates to {brand: “A”}. This means we would filter our collection by items whose brand property have a value of “A”.

MongoDB查询有一个JSON结构,我们在其中指定字段名和我们想过滤的值。因此,当我们调用countBrand(“A”)时,该查询被翻译为{brand:”A”}。这意味着我们将通过brand属性值为 “A “的项目来过滤我们的集合。

5. Writing a Derived Query Method

5.编写派生查询方法

A derived query method is any method inside our repository that doesn’t include a @Query annotation with a value. These methods are parsed by name by Spring so we don’t have to write the query.

派生查询方法是指我们的资源库中不包括带有@Query注解的任何方法,。这些方法被Spring按名称解析,所以我们不必写查询。

Since we already have a count() method in our CrudRepository, let’s create an example that counts by a specific brand:

既然我们在CrudRepository中已经有一个count()方法,让我们创建一个按特定品牌计数的例子:

Long countByBrand(String brand);

This method will count all documents whose brand property matches the parameter value.

该方法将计算所有品牌属性与参数值相匹配的文件

Now, let’s add it to our service:

现在,让我们把它添加到我们的服务中。

public long getCountBrandWithQueryMethod(String brand) {
    return repo.countByBrand(brand);
}

Then we ensure our method is behaving correctly by comparing it to a filtered stream count operation:

然后,我们通过与过滤后的流计数操作相比较,确保我们的方法行为正确。

@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);
}

This works great when we have to write only a few different queries. But, it can become hard to maintain if we need too many different count queries.

当我们只需要写几个不同的查询时,这很好。但是,如果我们需要太多不同的计数查询,它就会变得难以维护。

6. Using Dynamic Count Queries With Criteria

6.使用带有标准的动态计数查询

When we need something a little more robust, we can use Criteria with a Query object.

当我们需要更强大的东西时,我们可以使用CriteriaQuery对象。

But, to run a Query, we need MongoTemplate. It’s instantiated during startup and made available in SimpleMongoRepository in the mongoOperations field.

但是,要运行一个Query,我们需要MongoTemplate。它在启动时被实例化,并在SimpleMongoRepositorymongoOperations字段中可用。

One way to access it would be to extend SimpleMongoRepository and create a custom implementation instead of simply extending MongoRepository. But, there’s a simpler way. We can inject it into our service:

访问它的一个方法是扩展SimpleMongoRepository并创建一个自定义的实现,而不是简单地扩展MongoRepository。但是,还有一个更简单的方法。我们可以将其注入我们的服务中。

@Autowired
private MongoTemplate mongo;

Then we can create our new count method, passing the Query to the count() method in MongoTemplate:

然后我们可以创建新的计数方法,将Query传递给MongoTemplate中的count()方法。

public long getCountBrandWithCriteria(String brand) {
    Query query = new Query();
    query.addCriteria(Criteria.where("brand")
      .is(brand));
    return mongo.count(query, Car.class);
}

This approach is useful when we need to create dynamic queries. We have full control over how the projection is created.

当我们需要创建动态查询时,这种方法很有用。我们可以完全控制投影的创建方式。

6.1. Filter Using Example Object

6.1.使用Example Object的过滤器

The Criteria object also allows us to pass an Example object:

Criteria对象还允许我们传递一个Example对象:

public long getCountWithExampleCriteria(Car item) {
    Query query = new Query();
    query.addCriteria(Criteria.byExample(item));
    return mongo.count(query, Car.class);
}

This makes filtering by property easier, while still allowing dynamic parts.

这使得按属性过滤更容易,同时仍然允许动态部分。

7. Conclusion

7.结语

In this article, we saw different ways to use a count projection in Spring Data MongoDB with repository methods.

在这篇文章中,我们看到了在Spring Data MongoDB中用存储库方法使用计数投影的不同方法。

We used the methods available and also created new ones using different approaches. Furthermore, we created tests by comparing our count methods to listing all the objects in our collection. In the same vein, we learned why it’s not a good idea to count documents like that.

我们使用了现有的方法,也使用不同的方法创建了新的方法。此外,我们通过比较我们的计数方法和列出我们集合中的所有对象来创建测试。同样地,我们了解到为什么这样计算文件不是一个好主意。

Also, we went a little deeper and used MongoTemplate to create more dynamic count queries.

另外,我们更深入一点,使用MongoTemplate来创建更多的动态计数查询。

And as always, the source code is available over on GitHub.

一如既往,源代码可在GitHub上获得。