Spring Data mongodb聚合管道的分页结果

Ste*_*rck 5 spring-data-mongodb spring-boot spring-restcontroller spring-rest

我在对聚合管道的结果进行分页时遇到了一些麻烦。在查看了In spring data mongodb 如何实现分页进行聚合后,我想出了一个感觉像 hacky 的解决方案。我首先执行匹配查询,然后按我搜索的字段分组,并对结果进行计数,将值映射到一个私有类:

private long getCount(String propertyName, String propertyValue) {
    MatchOperation matchOperation = match(
        Criteria.where(propertyName).is(propertyValue)
    );
    GroupOperation groupOperation = group(propertyName).count().as("count");
    Aggregation aggregation = newAggregation(matchOperation, groupOperation);
    return mongoTemplate.aggregate(aggregation, Athlete.class, NumberOfResults.class)
        .getMappedResults().get(0).getCount();
}

private class NumberOfResults {
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,我就能够为我返回的页面对象提供一个“总”值:

public Page<Athlete> findAllByName(String name, Pageable pageable) {
    long total = getCount("team.name", name);
    Aggregation aggregation = getAggregation("team.name", name, pageable);
    List<Athlete> aggregationResults = mongoTemplate.aggregate(
        aggregation, Athlete.class, Athlete.class
    ).getMappedResults();
    return new PageImpl<>(aggregationResults, pageable, total);
}
Run Code Online (Sandbox Code Playgroud)

您可以看到获得结果总数的聚合与我要执行的实际聚合没有太大区别:

MatchOperation matchOperation = match(Criteria.where(propertyName).is(propertyValue));
SkipOperation skipOperation = skip((long) (pageable.getPageNumber() * pageable.getPageSize()));
LimitOperation limitOperation = limit(pageable.getPageSize());
SortOperation sortOperation = sort(pageable.getSort());
return newAggregation(matchOperation, skipOperation, limitOperation, sortOperation);
Run Code Online (Sandbox Code Playgroud)

这确实有效,但是,正如我所说的,感觉很糟糕。有没有办法获得 PageImpl 实例的计数,而不必本质上运行查询两次?

Jon*_*Jon 5

您的问题帮助我解决了与聚合分页相同的问题,因此我做了一些挖掘并提出了解决您问题的方法。我知道有点晚了,但有人可能会利用这个答案。我绝不是 Mongo 专家,因此如果我所做的做法不好或性能不佳,请随时告诉我。

使用组,我们可以将根文档添加到集合中并进行计数。

group().addToSet(Aggregation.ROOT).as("documents")
       .count().as("count"))
Run Code Online (Sandbox Code Playgroud)

这是我针对您所面临的几乎完全相同的问题的解决方案。

private Page<Customer> searchWithFilter(final String filterString, final Pageable pageable, final Sort sort) {
    final CustomerAggregationResult aggregationResult = new CustomerAggregationExecutor()
        .withAggregations(match(new Criteria()
                .orOperator(
                    where("firstName").regex(filterString),
                    where("lastName").regex(filterString))),
            skip((long) (pageable.getPageNumber() * pageable.getPageSize())),
            limit(pageable.getPageSize()),
            sort(sort),
            group()
                .addToSet(Aggregation.ROOT).as("documents")
                .count().as("count"))
        .executeAndGetResult(operations);
    return new PageImpl<>(aggregationResult.getDocuments(), pageable, aggregationResult.getCount());
}
Run Code Online (Sandbox Code Playgroud)

客户聚合结果.java

@Data
public class CustomerAggregationResult {

  private int count;
  private List<Customer> documents;

  public static class PageableAggregationExecutor {

    private Aggregation aggregation;

    public CustomerAggregationExecutor withAggregations(final AggregationOperation... operations) {
      this.aggregation = newAggregation(operations);
      return this;
    }

    @SuppressWarnings("unchecked")
    public CustomerAggregationResult executeAndGetResult(final MongoOperations operations) {
        return operations.aggregate(aggregation, Customer.class, CustomerAggregationResult.class)
            .getUniqueMappedResult();
    }

  }
}
Run Code Online (Sandbox Code Playgroud)

真的希望这有帮助。

编辑:我最初使用 List 创建了一个通用的 PageableAggregationResult ,但是当我传递没有 T 类型的 PageableAggregationResult.class 时,这会返回一个 IllegalArgumentException 。如果我找到解决方案,我将编辑这个答案,因为我希望能够聚合多个集合最终。