分页、排序与投影
使用 Pageable 返回分页结果,动态排序,并使用接口投影限制获取的列
分页、排序与投影 是 CoddyKit 上的免费 Java Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
为什么需要分页
在一次查询中返回数千行会浪费内存和带宽。分页会一次加载一页数据。Spring Data 通过 Pageable 抽象提供分页支持。
Pageable 与 PageRequest
使用 PageRequest.of(page, size) 创建分页对象。将其传递给任何返回 Page<T> 的仓储方法。
Pageable pageable = PageRequest.of(0, 20); // page 0, 20 per page
Page<User> page = userRepo.findAll(pageable);
System.out.println("Total: " + page.getTotalElements());
System.out.println("Pages: " + page.getTotalPages());
System.out.println("Content: " + page.getContent().size());使用 PageRequest 排序
使用 Sort 对象为 PageRequest 添加排序。通过 Sort.by().and() 链接多个字段。
Pageable sorted = PageRequest.of(0, 20,
Sort.by(Sort.Direction.DESC, "createdAt")
.and(Sort.by("name")));
Page<User> page = userRepo.findAll(sorted);切片与 Page
Page<T> 会执行 COUNT 查询来计算总页数。Slice<T> 不会执行 COUNT 查询,只知道是否存在下一页。当不需要总数时,请在无限滚动中使用 Slice。
public interface UserRepository extends JpaRepository<User, Long> {
Slice<User> findByStatus(UserStatus status, Pageable pageable);
}Spring MVC 集成
Spring MVC 会根据查询参数自动解析 Pageable:?page=0&size=20&sort=name,asc。无需手动解析。
@GetMapping("/users")
public Page<UserDto> list(Pageable pageable) {
return userRepo.findAll(pageable).map(UserDto::from);
}
// GET /users?page=0&size=10&sort=name,asc接口投影
定义一个接口,其中包含与实体字段名称匹配的获取方法。Spring Data 只返回这些列,从而减少数据传输并隐藏内部实现。
public interface UserSummary {
Long getId();
String getName();
String getEmail();
}
List<UserSummary> findByStatus(UserStatus status);基于类的(数据传输对象)投影
使用带有构造方法的数据传输对象记录或类。Spring Data 会为选定的列生成 JPQL 构造方法表达式。
public record UserDto(Long id, String name, String email) {}
@Query("SELECT new com.example.UserDto(u.id, u.name, u.email) FROM User u WHERE u.status = :s")
List<UserDto> findSummaries(@Param("s") UserStatus status);动态投影
将投影类型作为泛型参数传递给仓储方法,以便在调用位置选择投影。
public interface UserRepository extends JpaRepository<User, Long> {
<T> List<T> findByStatus(UserStatus status, Class<T> type);
}
// Usage:
List<UserSummary> summaries = repo.findByStatus(ACTIVE, UserSummary.class);
List<User> full = repo.findByStatus(ACTIVE, User.class);键集分页(偏移量与键集)
偏移分页(OFFSET N)在偏移量较大时性能会下降。键集分页(使用 WHERE id > lastSeenId)无论分页深度如何都是 O(1),更适合深层分页。
// Keyset pagination:
@Query("SELECT u FROM User u WHERE u.id > :lastId ORDER BY u.id LIMIT :size")
List<User> findPage(@Param("lastId") Long lastId, @Param("size") int size);优化总数统计
在 @Query 中使用 countQuery,提供经过优化的统计查询,从而避免主查询中的高开销 JOIN。
@Query(value = "SELECT u FROM User u JOIN FETCH u.orders WHERE u.status = :s",
countQuery = "SELECT COUNT(u) FROM User u WHERE u.status = :s")
Page<User> findActiveWithOrders(@Param("s") UserStatus s, Pageable p);返回带有页面元数据的 ResponseEntity
在 REST 响应中包含页面元数据(totalElements、totalPages、页码和页面大小),让客户端知道如何进行页面导航。
@GetMapping("/users")
public ResponseEntity<Map<String, Object>> list(Pageable p) {
Page<UserDto> page = repo.findAll(p).map(UserDto::from);
return ResponseEntity.ok(Map.of(
"content", page.getContent(),
"totalElements", page.getTotalElements(),
"totalPages", page.getTotalPages()
));
}快速检查
Page 与切片之间的主要区别是什么?
回顾
使用 Pageable + PageRequest.of() 进行偏移分页。Spring MVC 会从查询参数解析 Pageable。使用 Slice 避免 COUNT 查询。使用接口投影或数据传输对象投影来限制获取的列。对于较大偏移量,请使用键集分页。
常见问题解答
「分页、排序与投影」课时是免费的吗?
是的 — 「分页、排序与投影」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「分页、排序与投影」这节课中我会学到什么?
使用 Pageable 返回分页结果,动态排序,并使用接口投影限制获取的列 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「分页、排序与投影」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。