工作中用到的数据库是 MongoDB,持久层采用的是 Spring Data MongoDB,经常遇到需要分页的场景,这里列举几个常见的用法。

1. 普通分页

普通分页指的是使用 skiplimit 进行分页,示例

  /**
   * 分页查询
   *
   * @param pageNo 页码 (默认 1)
   * @param pageSize 每页数量(默认 10)
   * @param query 查询条件
   * @param clazz 返回类型
   * @return 分页结果
   * @param <T> 返回类型
   */
  public <T> Page<T> page(Integer pageNo, Integer pageSize, Query query, Class<T> clazz) {
    // 设置分页参数
    pageNo = pageNo == null || pageNo <= 0 ? 1 : pageNo;
    pageSize = pageSize == null || pageSize <= 0 ? 10 : pageSize;
    if (query == null) {
      query = new Query();
    }
    query.skip((long) (pageNo - 1) * pageSize);
    query.limit(pageSize);
    List<T> elements = mongoTemplate.find(query, clazz);
    long totalNum = mongoTemplate.count(query, clazz);
    return PageableExecutionUtils.getPage(
        elements, PageRequest.of(pageNo - 1, pageSize), () -> totalNum);
  }

注意事项

  1. mongoTemplate 分页从 0 开始
  2. 当数据量多,分页会越来越慢

2. 聚合分页

当涉及到复杂查询时,往往使用聚合查询方式,这时候对应总页数和总条数需要单独处理,示例

  /**
   * 分页聚合查询
   *
   * @param pageNo 页码 (默认 1)
   * @param pageSize 每页数量(默认 10)
   * @param operations 查询条件
   * @param collectionName 集合名称
   * @param outputType 返回类型
   * @return 分页结果
   * @param <T> 返回类型
   */
  public <T> Page<T> pageAgg(
      Integer pageNo,
      Integer pageSize,
      List<AggregationOperation> operations,
      String collectionName,
      Class<T> outputType) {
    // 设置分页参数
    pageNo = pageNo == null || pageNo <= 0 ? 1 : pageNo;
    pageSize = pageSize == null || pageSize <= 0 ? 10 : pageSize;
    if (CollUtil.isEmpty(operations)) {
      operations = new ArrayList<>();
    }
    List<AggregationOperation> totalOperations = new ArrayList<>(operations);
    totalOperations.add(Aggregation.count().as("count"));
    long totalNum =
        Optional.ofNullable(
                mongoTemplate
                    .aggregate(
                        Aggregation.newAggregation(totalOperations), collectionName, Document.class)
                    .getUniqueMappedResult())
            .map(doc -> ((Integer) doc.get("count")).longValue())
            .orElse(0L);

    List<AggregationOperation> pageOperations = new ArrayList<>(operations);
    pageOperations.add(Aggregation.skip((long) (pageNo - 1) * pageSize));
    pageOperations.add(Aggregation.limit(pageSize));
    List<T> elements =
        mongoTemplate
            .aggregate(Aggregation.newAggregation(pageOperations), collectionName, outputType)
            .getMappedResults();

    return PageableExecutionUtils.getPage(
        elements, PageRequest.of(pageNo - 1, pageSize), () -> totalNum);
  }

3. 去重分页

当需要查询数据库某个字段,并且需要去重和分页时,会发现 MongoDB 的去重是不支持分页的,即使在 mongoTemplate 查询时,传入分页信息,也是无效的,会一次性返回所有数据,这时候需要借助聚合查询实现去重分页,示例

  /**
   * 分页去重查询(不建议数据量过大的数据库使用)
   *
   * @param pageNo 页码 (默认 1)
   * @param pageSize 每页数量(默认 10)
   * @param column 去重字段
   * @param criteria 查询条件
   * @param collectionName 集合名称
   * @param outputType 返回类型
   * @return 分页结果
   * @param <T> 返回类型
   */
  public <T> Page<T> pageDistinct(
      Integer pageNo,
      Integer pageSize,
      String column,
      Criteria criteria,
      String collectionName,
      Class<T> outputType) {

    List<AggregationOperation> operations = new ArrayList<>();
    operations.add(Aggregation.group(column));
    operations.add(Aggregation.project(Aggregation.fields(ID, column)));
    if (criteria != null) {
      operations.add(Aggregation.match(criteria));
    }
    operations.add(Aggregation.sort(Direction.ASC, ID));
    Page<Document> page = pageAgg(pageNo, pageSize, operations, collectionName, Document.class);
    List<T> elements =
        page.getContent().stream().map(doc -> doc.get(ID, outputType)).collect(Collectors.toList());
    return PageableExecutionUtils.getPage(elements, page.getPageable(), page::getTotalElements);
  }

注意事项

  1. 不建议数据量过大的数据库使用

4. 分页查询所有数据

当需要查询所有数据时,如果采用普通分页一页页查询,数据量大时,会非常缓慢,这时候可以采用游标分页来加快分页,示例

  /**
   * 分页查询所有数据
   *
   * <p>每次查询数量不建议超过 500,排序为 _id 升序
   *
   * @param query 查询条件
   * @param clazz 返回类型
   * @param batchSize 每次查询数量
   * @return 分页结果
   * @param <T> 返回类型
   */
  public <T> List<T> pageAll(Query query, Class<T> clazz, int batchSize) {
    if (batchSize <= 0) {
      throw new IllegalArgumentException("batchSize must be greater than 0");
    }
    if (query == null) {
      query = new Query();
    }
    query.with(Sort.by(Direction.ASC, ID));
    List<T> result = new ArrayList<>();
    try (MongoCursor<Document> cursor =
        mongoTemplate
            .getCollection(mongoTemplate.getCollectionName(clazz))
            .find(query.getQueryObject())
            .sort(query.getSortObject())
            .noCursorTimeout(true)
            .batchSize(batchSize)
            .iterator()) {
      while (cursor.hasNext()) {
        // 转换
        Document document = cursor.next();
        T t = mongoTemplate.getConverter().read(clazz, document);
        result.add(t);
      }
    }
    return result;
  }

参考资料

最后修改:2023 年 04 月 18 日
如果觉得我的文章对你有用,请随意赞赏