MongoTemplate的分页功能

37

我对Pageable有疑问:

Query query = new Query().with(new PageRequests(page, size))

如何使用MongoTemplate执行它?我没有看到返回 Page<T> 的单个方法。

7个回答

85

确实,MongoTemplate 没有带有 PageablesfindXXX 方法。

但是你可以使用 Spring Repository 中的 PageableExecutionUtils 实现这一点。

在你的示例中,它会像这样:

Pageable pageable = new PageRequests(page, size);
Query query = new Query().with(pageable);
List<XXX> list = mongoTemplate.find(query, XXX.class);
return PageableExecutionUtils.getPage(
                       list, 
                       pageable, 
                       () -> mongoTemplate.count(Query.of(query).limit(-1).skip(-1), XXX.class));

与原始的Spring Data Repository类似,PageableExecutionUtils将执行计数请求并将其封装到一个漂亮的Page中。

在这里,您可以看到Spring正在执行相同的操作。


1
来自Spring Data commons: https://github.com/spring-projects/spring-data-commons/blob/master/src/main/java/org/springframework/data/repository/support/PageableExecutionUtils.java - d0x
@清单:是的,就像mongodb一样:https://github.com/spring-projects/spring-data-mongodb/blob/3012bcd575d85ad7cd11e07962fc168d4bbc98b2/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java#L275-L285 - TOUDIdel
3
如果您的查询返回数十万或数百万行数据但没有分页,那么将所有这些文档读入内存会使服务器崩溃。更好的方法是使用聚合框架。 - Madbreaks
1
@d0x,您能否建议一下为什么不能使用 list.size() 来获取元素的总数? - java_newbie
query.limit(-1).skip(-1) 这样的话,你觉得用 query.with(Pageable.unpaged()) 怎么样? - Michael R
显示剩余3条评论

22

根据d0x的答案,并查看spring代码。我使用这个变体,它依赖于spring-boot-starter-data-mongodb,而不需要添加spring data commons。

@Autowired
private MongoOperations mongoOperations;

@Override
public Page<YourObjectType> searchCustom(Pageable pageable) {
    Query query = new Query().with(pageable);
    // Build your query here

    List<YourObjectType> list = mongoOperations.find(query, YourObjectType.class);
    long count = mongoOperations.count(query, YourObjectType.class);
    Page<YourObjectType> resultPage = new PageImpl<YourObjectType>(list , pageable, count);
    return resultPage;
}

9
提供信息:为了得到正确的计数值 - 查询应该重置:跳过,限制值。计数调用应该是:mongoOperations.count(query.skip(-1).limit(-1), YourObjectType.class)。否则会返回查询的错误计数。 - eg04lt3r
1
@eg04lt3r 你说得对。我花了一整天的时间试图理解和修复它。它实际上返回的是当前页面的数量,而不是结果的总数。添加跳过和限制解决了这个问题。 - undefined

19

MongoTemplate没有返回Page的方法,find()方法只返回普通的List

在MongoDB查询中,with(new PageRequests(page, size)被内部使用来调整skiplimit(之前可能会有一个count查询)。

Page可以与MongoDB repositories结合使用,它是Spring data repositories的一个专门案例。

因此,你需要使用MongoRepositoryPage findAll(Pageable pageable)来获取分页结果(实际上是从PagingAndSortingRepository继承下来的)。


4
到目前为止所有的答案都是执行了两次查询。这通常不是你想要的。下面提供一种使用聚合来仅运行一次查询的解决方案。
public class Result<T> {
    int totalCount;
    List<T> objects;
}

public <T, R extends Result<T>> R executePaged(Class<T> inputType,
                                               Criteria criteria,
                                               Pageable pageable,
                                               Class<R> resultType) {
    var match = Aggregation.match(criteria);
    var facets = Aggregation
            .facet(
                    Aggregation.sort(pageable.getSort()),
                    Aggregation.skip(pageable.getOffset()),
                    Aggregation.limit(pageable.getPageSize())
            ).as("objects")
            .and(
                    Aggregation.count().as("count")
            ).as("countFacet");
    var project = Aggregation
            .project("objects")
            .and("$countFacet.count").arrayElementAt(0).as("totalCount");

    var aggregation = Aggregation.newAggregation(match, facets, project);
    return mongoTemplate.aggregate(aggregation, inputType, resultType)
            .getUniqueMappedResult();
}

注意事项:

  • 扩展和参数化Result<T>以通过 resultType 传递,否则反序列化程序不知道要使用什么类
  • pageable必须使用Sort创建
  • 如果需要,之后仍然可以创建PageImpl

示例:

class Person {
    String name;
    Date birthdate;
}

class PersonResult extends Result<Person>{}

PersonResult result = executePaged(Person.class,
      Criteria.where("name").is("John"),
      PageRequest.of(3, 50).withSort(Sort.by("birthdate")),
      PersonResult.class);

System.out.println("Total #Johns: " + result.totalCount);
System.out.println("Johns on this page: " + result.objects);

这个问题是它不会利用索引进行排序。$facets 不使用索引,强制整个查询使用磁盘进行排序。 - v1shnu

3

默认情况下,Spring MongoDB模板没有按页查找的方法。它搜索并返回整个记录列表。我尝试过以下方法,可以使用:

Pageable pageable = new PageRequests(0, 10);                              
Query query = new Query(criteria); 
query.with(pageable);   
List<User> lusers = mt.find(query, User.class);   
Page<User> pu = new PageImpl<>(lusers, pageable, mongoTemplate.count(newQuery(criteria), User.class));

1

这里提供的解决方案都没有在我的情况下起作用。 我尝试了下面这个来自 Medium 帖子的解决方案,但它从未返回分页结果,而是返回了所有结果,这不是我期望的。

return PageableExecutionUtils.getPage(
        mongoTemplate.find(query, ClassName.class),
        pageable,
        () -> mongoTemplate.count(query.skip(0).limit(0), ClassName.class)
);

所以我发现了一个更好的方法来解决它,在我的情况下起作用:

return PageableExecutionUtils.getPage(
            mongoTemplate.find(query.with(pageable), ClassName.class),
            pageable,
            () -> mongoTemplate.count(query, ClassName.class));

你需要在查询中添加pageRequest,例如在你代码的第一个片段中加入query.with(pageRequest)。其余部分没问题。skip和limit用于配置总页数逻辑。 - Surendra Meena

-1
return type Mono<Page<Myobject>>...

return this.myobjectRepository.count()
        .flatMap(ptiCount -> {
          return this.myobjectRepository.findAll(pageable.getSort())
            .buffer(pageable.getPageSize(),(pageable.getPageNumber() + 1))
            .elementAt(pageable.getPageNumber(), new ArrayList<>())
            .map(ptis -> new PageImpl<Myobject>(ptis, pageable, ptiCount));
        });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接