ページネーション、ソート、プロジェクション
Pageableでページ単位の結果を返し、動的にソートして、インターフェースプロジェクションで取得する列を絞り込みます。
「ページネーション、ソート、プロジェクション」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
なぜページネーションを使うのか
数千行を 1 回のクエリで返すと、メモリと帯域幅を無駄に消費します。ページネーションを使うと、1 ページずつ読み込めます。Spring Data では、Pageable 抽象化によってページネーションを利用できます。
Pageable と PageRequest
PageRequest.of(page, size) を使って Pageable を作成します。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);Slice と 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インターフェースプロジェクション
エンティティのフィールド名と一致する getter メソッドを持つインターフェースを定義します。Spring Data はそのカラムだけを返すため、データ転送量を削減し、内部実装を隠せます。
public interface UserSummary {
Long getId();
String getName();
String getEmail();
}
List<UserSummary> findByStatus(UserStatus status);クラスベース(DTO)プロジェクション
コンストラクターを持つ DTO の record またはクラスを使用します。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);Keyset ページネーション(Offset と Keyset)
オフセットページネーション(OFFSET N)は、オフセットが大きくなると性能が低下します。Keyset ページネーション(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、number、size)を含めます。
@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 と Slice の主な違いは何ですか?
まとめ
オフセットページネーションには Pageable と PageRequest.of() を使用します。Spring MVC はクエリパラメーターから Pageable を解決します。COUNT クエリを避けるには Slice を使用します。取得するカラムを絞るには、インターフェースプロジェクションまたは DTO プロジェクションを使用します。大きなオフセットには Keyset ページネーションを使用します。
よくある質問
「ページネーション、ソート、プロジェクション」レッスンは無料ですか?
はい。「ページネーション、ソート、プロジェクション」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「ページネーション、ソート、プロジェクション」で何を学びますか?
Pageableでページ単位の結果を返し、動的にソートして、インターフェースプロジェクションで取得する列を絞り込みます。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「ページネーション、ソート、プロジェクション」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- JPAアノテーションによるエンティティマッピング
- Spring Dataリポジトリとクエリメソッド
- 1対多と多対多のリレーションシップ
- ページネーション、ソート、プロジェクション