MongoTemplate
结合 Spring Data Commons 中的 Pageable
和 Sort
接口,可以非常方便地实现分页和排序功能。
以下是如何使用 MongoTemplate
进行分页和排序:
核心概念:
org.springframework.data.domain.Sort
:
Sort.by(Sort.Direction direction, String... properties)
或 Sort.by(List orders)
创建。Sort.Order
对象包含排序方向 (ASC
或 DESC
) 和属性名。org.springframework.data.domain.Pageable
:
Sort
)。org.springframework.data.domain.PageRequest
。org.springframework.data.domain.Page
:
使用步骤:
1. 排序 (Sorting)
你可以直接将 Sort
对象应用到 Query
中。
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Criteria.where;
// 假设有一个 User 类和 MongoTemplate 实例
// MongoTemplate mongoTemplate = ...;
// 示例1: 按单个字段升序排序
Query queryAsc = new Query(where("status").is("ACTIVE"));
queryAsc.with(Sort.by(Sort.Direction.ASC, "username")); // 按 username 升序
List<User> sortedUsersAsc = mongoTemplate.find(queryAsc, User.class);
// 示例2: 按单个字段降序排序
Query queryDesc = new Query(where("status").is("ACTIVE"));
queryDesc.with(Sort.by(Sort.Direction.DESC, "registrationDate")); // 按 registrationDate 降序
List<User> sortedUsersDesc = mongoTemplate.find(queryDesc, User.class);
// 示例3: 按多个字段排序 (先按 lastName 升序,再按 firstName 升序)
Query queryMultiSort = new Query(where("department").is("Sales"));
Sort multiSort = Sort.by(
Sort.Order.asc("lastName"),
Sort.Order.asc("firstName")
);
queryMultiSort.with(multiSort);
// 或者更简洁的方式:
// queryMultiSort.with(Sort.by("lastName", "firstName").ascending());
// 或者
// queryMultiSort.with(Sort.by(Sort.Direction.ASC, "lastName", "firstName"));
List<User> multiSortedUsers = mongoTemplate.find(queryMultiSort, User.class);
// 示例4: 混合排序方向
Query queryMixedSort = new Query(where("points").gt(100));
Sort mixedSort = Sort.by(
Sort.Order.desc("points"), // 按 points 降序
Sort.Order.asc("username") // 然后按 username 升序
);
queryMixedSort.with(mixedSort);
List<User> mixedSortedUsers = mongoTemplate.find(queryMixedSort, User.class);
2. 分页 (Pagination)
分页通常与排序结合使用。你需要创建一个 Pageable
对象,并将其应用到 Query
中。
MongoTemplate
本身没有直接返回 Page
的 find
方法。你需要分别获取数据列表和总数,然后手动构建 Page
对象,或者使用辅助方法。
方法一:手动获取数据和总数,然后构建 PageImpl
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Criteria.where;
// MongoTemplate mongoTemplate = ...;
// 分页参数
int pageNumber = 0; // 第 0 页 (第一页)
int pageSize = 10; // 每页 10 条记录
Sort sort = Sort.by(Sort.Direction.DESC, "registrationDate"); // 按注册日期降序
// 创建 Pageable 对象
Pageable pageable = PageRequest.of(pageNumber, pageSize, sort);
// 创建查询条件
Query query = new Query(where("status").is("ACTIVE"));
// 1. 获取总记录数 (不应用分页的 skip 和 limit, 但应用排序是可选的,通常不需要)
long totalCount = mongoTemplate.count(query, User.class); // 注意:这里的 query 不应该包含分页参数 (skip/limit)
// 2. 应用分页参数到查询中
query.with(pageable); // 这会设置 query.setSkip() 和 query.setLimit() 以及 query.setSortObject()
// 3. 获取当前页的数据列表
List<User> usersOnPage = mongoTemplate.find(query, User.class);
// 4. 构建 Page 对象
Page<User> userPage = new PageImpl<>(usersOnPage, pageable, totalCount);
// 使用 Page 对象
System.out.println("Total elements: " + userPage.getTotalElements());
System.out.println("Total pages: " + userPage.getTotalPages());
System.out.println("Current page number: " + userPage.getNumber());
System.out.println("Page size: " + userPage.getSize());
System.out.println("Content: " + userPage.getContent());
方法二:使用 MongoTemplate
的一些变体或自定义 Repository (推荐用于更简洁的代码)
虽然 MongoTemplate
核心 find
方法不直接返回 Page
,但在实际项目中,你通常会:
创建自定义 Repository 接口继承 PagingAndSortingRepository
或 MongoRepository
:
这些接口提供了开箱即用的分页和排序方法,如 findAll(Pageable pageable)
。这是最推荐的方式,因为它更简洁,也符合 Spring Data 的设计理念。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface UserRepository extends MongoRepository<User, String> {
Page<User> findByStatus(String status, Pageable pageable);
}
// 在 Service 中使用:
// @Autowired
// private UserRepository userRepository;
//
// Pageable pageable = PageRequest.of(0, 10, Sort.by("username"));
// Page userPage = userRepository.findByStatus("ACTIVE", pageable);
使用 Spring Data Projections (如果只需要部分字段):
结合 Repository 和 Pageable,可以返回投影接口或 DTO 的分页结果。
通过 MongoOperations
(MongoTemplate 实现的接口) 扩展方法 (不常见,但可以实现):
可以创建自己的工具类或服务方法来封装分页逻辑。
Query
对象与 Pageable
的交互:
当你调用 query.with(pageable)
时,Spring Data MongoDB 会从 Pageable
对象中提取以下信息并设置到 Query
对象中:
pageable.getOffset()
-> query.skip(offset)
pageable.getPageSize()
-> query.limit(pageSize)
pageable.getSort()
-> query.with(sort)
(设置排序对象)示例:结合查询条件、分页和排序
// MongoTemplate mongoTemplate = ...;
// 分页参数
int pageNumber = 1; // 第二页 (页码从0开始)
int pageSize = 5;
Sort sort = Sort.by(Sort.Order.asc("lastName"), Sort.Order.desc("points"));
Pageable pageable = PageRequest.of(pageNumber, pageSize, sort);
// 查询条件:部门为 "Support" 且激活状态为 true
Query query = new Query(
new Criteria().andOperator(
where("department").is("Support"),
where("isActive").is(true)
)
);
// 获取总数 (基于未分页的查询)
long totalCount = mongoTemplate.count(Query.of(query).limit(-1).skip(-1), User.class); // 克隆query并移除分页
// 应用分页到原始查询对象
query.with(pageable);
// 获取当前页数据
List<User> pagedUsers = mongoTemplate.find(query, User.class);
// 构建 Page 对象
Page<User> userPage = new PageImpl<>(pagedUsers, pageable, totalCount);
// 使用结果
System.out.println("Content for page " + userPage.getNumber() + ": " + userPage.getContent());
System.out.println("Total users matching criteria: " + userPage.getTotalElements());
关于 mongoTemplate.count()
的注意事项:
mongoTemplate.count()
的 Query
对象不应该包含 skip()
和 limit()
设置,否则 count()
的结果会是你期望的当前页数据量(如果 limit
小于总数)而不是总匹配记录数。count()
操作通常是不必要的,MongoDB 在计算数量时会忽略它。count
。Query countQuery = Query.of(query); // 克隆查询条件
countQuery.limit(-1); // 确保没有 limit
countQuery.skip(-1); // 确保没有 skip
countQuery.setSortObject(null); // 移除排序
long total = mongoTemplate.count(countQuery, User.class);
或者更简单地,如果你的 query
在调用 count
之前还没有被 with(pageable)
修改过,可以直接用。总结:
Sort
对象定义排序规则,并用 query.with(Sort)
应用。PageRequest.of(pageNumber, pageSize, sort)
创建 Pageable
对象。query.with(Pageable)
将分页和排序信息应用到查询。mongoTemplate.count()
获取总记录数和 mongoTemplate.find()
获取当前页数据。PageImpl
将结果封装成 Page
。MongoRepository
) 来处理分页和排序,代码会更简洁且不易出错。