0Pricing
Java Academy · レッスン

ページネーション、ソート、プロジェクション

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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. JPAアノテーションによるエンティティマッピング
  2. Spring Dataリポジトリとクエリメソッド
  3. 1対多と多対多のリレーションシップ
  4. ページネーション、ソート、プロジェクション
← Java Academyに戻る