工作中用到的数据库是 MongoDB,持久层采用的是 Spring Data MongoDB,经常遇到需要分页的场景,这里列举几个常见的用法。
1. 普通分页
普通分页指的是使用 skip
和 limit
进行分页,示例
/**
* 分页查询
*
* @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);
}
注意事项
- mongoTemplate 分页从 0 开始
- 当数据量多,分页会越来越慢
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);
}
注意事项
- 不建议数据量过大的数据库使用
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;
}