SpringのPageableとSortでページング一覧APIを最短実装!初心者向け完全ガイド
生徒
「Spring Bootで一覧APIを作るときに、データをページ分けしたいんですけど、どうすればいいですか?」
先生
「SpringにはPageableとSortという便利な機能があって、それを使えば簡単にページング処理やソートが実装できますよ。」
生徒
「えっ、それってController側に全部書くんですか?」
先生
「いい質問ですね。実はSpring Data JPAと組み合わせると、リポジトリレイヤーでも簡潔に書けるんです。さっそく一緒に実装してみましょう!」
1. Pageableとは?ページング処理の基本
Spring FrameworkやSpring Bootで一覧データを扱うとき、すべてのレコードを一度に返すのは非効率です。特にデータ件数が多くなると、レスポンスのサイズが大きくなり、APIのパフォーマンスやユーザー体験に影響が出てきます。
その解決策としてよく使われるのが、ページング機能です。SpringではPageableインターフェースを使えば、何件目から何件取得するかといった情報を簡単に指定できます。
さらに、フロントエンドと連携する場合でも、page(ページ番号)、size(ページサイズ)、sort(ソート条件)といったクエリパラメータをURLに追加するだけで、柔軟にページングが可能になります。
2. Spring Data JPAでPageableを使う方法
Spring Data JPAでは、リポジトリのインターフェースにPageableを引数として渡すだけで、自動的にページング処理が適用されます。
public interface BookRepository extends JpaRepository<Book, Long> {
Page<Book> findAll(Pageable pageable);
}
そしてControllerでPageableを受け取ると、クエリパラメータに応じたページング処理が実現できます。
@RestController
@RequestMapping("/books")
public class BookController {
private final BookRepository bookRepository;
public BookController(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@GetMapping
public Page<Book> getBooks(Pageable pageable) {
return bookRepository.findAll(pageable);
}
}
例えば、/books?page=0&size=5のように呼び出すと、最初の5件が取得されます。
3. Sortを使った並び順の変更
並び順(ソート)も、Sortを通じてPageableに組み込むことができます。ユーザー一覧をname昇順で表示したい場合、?sort=name,ascとするだけです。
複数条件のソートも可能で、?sort=name,asc&sort=id,descと指定することで、まず名前の昇順、その次にIDの降順になります。
リクエストURL例:
/books?page=1&size=10&sort=publishedDate,desc
4. デフォルト値の指定やバリデーションを加える方法
Pageableの初期値を指定したい場合は、@PageableDefaultアノテーションを使うことで、デフォルトのページサイズやソート条件を設定できます。
@GetMapping("/books/default")
public Page<Book> getBooksDefault(
@PageableDefault(size = 5, sort = "title", direction = Sort.Direction.ASC)
Pageable pageable) {
return bookRepository.findAll(pageable);
}
このようにしておくと、クエリパラメータが指定されなかった場合でも、安定したレスポンスが返せます。
5. ページングレスポンスの中身を理解しよう
Page<T>型の戻り値には、対象のデータだけでなく、ページ全体に関するメタ情報も含まれています。
- content:現在のページのデータリスト
- totalElements:全件数
- totalPages:総ページ数
- number:現在のページ番号
- size:ページサイズ
- first / last:先頭ページか末尾ページかの判定
この情報を使えば、「次へ」「前へ」などのページネーションリンクも実装しやすくなります。
6. REST APIでページングとソートを活用する実例
実務では、検索や一覧画面において、ページングとソートを同時に扱うことが一般的です。
Spring BootでREST APIを作成する際は、以下のようなリクエストURLが実際に使われます。
/books?page=2&size=20&sort=author,desc
APIの戻り値はJSON形式で、フロントエンドに渡しやすく、AngularやReact、Vue.jsなどとも相性抜群です。
7. ページングとソートをカスタムクラスに変換する
Page<T>はそのままだと使いにくい場合もあるため、レスポンスを独自DTO(Data Transfer Object)に変換するのもおすすめです。
例えば、以下のようなレスポンス構造に変換できます:
public class BookPageResponse {
private List<BookDto> content;
private int currentPage;
private int totalPages;
private long totalElements;
// getter/setter 略
}
8. PageableとSortを使うときの注意点
最後に、ページングとソートを使う際のよくある落とし穴を整理しておきます。
- ソート対象のフィールド名が間違っていると実行時エラーになる
- デフォルトページサイズが大きすぎるとレスポンスが重くなる
- ソート条件がユーザー入力のままだとSQLインジェクションの可能性も
こうしたリスクを避けるには、制限を設ける・ログ出力を強化する・明示的なバリデーションを加えることが推奨されます。
まとめ
本記事では、Spring Bootにおいて非常に強力なツールであるPageableとSortを用いた、ページング一覧APIの実装方法について詳しく解説してきました。WebアプリケーションやモダンなSPA(Single Page Application)を構築する際、大量のデータを一度に取得してクライアントに返却することは、サーバー負荷の増大やネットワーク帯域の圧迫、そしてユーザー体験(UX)の低下を招く大きな要因となります。そのため、実務レベルの開発において、適切なページング処理とソート機能の実装は避けて通れない必須スキルと言えるでしょう。
ページング実装の重要性とSEO効果
検索エンジン最適化(SEO)の観点からも、ページングは非常に重要です。大量のコンテンツを適切に分割し、関連性の高いデータを上位に表示させることは、クローラーの巡回効率を高め、ユーザーが必要な情報に素早くアクセスできる環境を提供することに直結します。JavaのSpring Frameworkが提供するPageableインターフェースを利用することで、開発者は複雑なSQLのLIMIT句やOFFSET句を直接記述することなく、オブジェクト指向的なアプローチでこれらの機能を統合できます。
サンプルプログラム:より実践的なサービス層での活用
これまでの章ではリポジトリとコントローラーの連携を見てきましたが、ここでは実際のビジネスロジックを想定し、サービス層(Service Layer)を介したより堅牢な実装サンプルを紹介します。DTO(Data Transfer Object)への変換処理も含めることで、APIの外部仕様とデータベースのエンティティ構造を分離し、保守性の高いコードを実現します。
@Service
@Transactional(readOnly = true)
public class BookService {
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
/**
* 書籍一覧をページング形式で取得し、DTOに変換して返却します。
* @param pageable ページング情報
* @return 変換済みのページオブジェクト
*/
public Page<BookResponseDto> findAllBooks(Pageable pageable) {
// RepositoryからエンティティのPageを取得
Page<Book> bookPage = bookRepository.findAll(pageable);
// エンティティからDTOへ変換(mapメソッドを活用)
return bookPage.map(book -> new BookResponseDto(
book.getId(),
book.getTitle(),
book.getAuthor(),
book.getPublishedDate()
));
}
}
このように、Page.map()メソッドを使用することで、ページングのメタ情報を維持したまま、中身のコンテンツだけをスマートに変換することが可能です。これは、APIレスポンスのセキュリティを確保し、必要なフィールドのみを公開する際に非常に有効な手法です。
PageableとSortを使いこなすためのヒント
さらに高度な活用方法として、複数のソート条件をプログラム内で動的に生成することも可能です。例えば、特定の条件下では「作成日」の降順、それ以外では「タイトル」の昇順といった制御が必要な場合、以下のようにSortオブジェクトを個別に定義できます。
// 動的なソート条件の生成例
Sort sort = Sort.by("publishedDate").descending()
.and(Sort.by("title").ascending());
Pageable customPageable = PageRequest.of(pageNumber, pageSize, sort);
これにより、URLパラメータに依存しない、システム内部での厳密な並び順制御が可能になります。Spring Bootの強力なエコシステムを理解し、これらの機能を適切に組み合わせることで、スケーラビリティに優れたバックエンドAPIを短期間で構築できるようになるでしょう。
生徒
「先生、ありがとうございました!Pageableを使うだけで、あんなに面倒だったページングの計算やSQL発行が自動化されるなんて驚きです。今までわざわざ件数をカウントするクエリを別で書いていたのが嘘みたいです。」
先生
「そうですね。Page<T>を戻り値にするだけで、全件数(totalElements)や総ページ数(totalPages)まで取得してくれるのは、Spring Data JPAの大きなメリットの一つです。フロントエンドのページネーション部品を作る時にも、その情報がそのまま使えるので非常に効率的ですよ。」
生徒
「はい、JSONレスポンスの中にlast: trueとかfirst: trueというフラグが入っているのを見て、これなら『次のページへ』ボタンの活性・非活性の制御も簡単にできるなと思いました。ソートについても、URLに?sort=price,descと付けるだけでいいのは直感的ですね。」
先生
「その通りです。ただ、記事の最後でも触れたように、ソート対象のフィールド名はJavaのエンティティのプロパティ名と一致させる必要があります。もし間違えると、PropertyReferenceExceptionというエラーが発生してアプリが止まってしまうので、そこだけは注意しましょうね。」
生徒
「プロパティ名ですね、気をつけます!あと、デフォルト値を設定する@PageableDefaultも便利そうでした。これを設定しておけば、もしユーザーがパラメータを何も指定しなくても、システムがパンクするような大量データ取得を防げますもんね。」
先生
「素晴らしい洞察です。パフォーマンスとセキュリティの両面を意識するのはエンジニアとして大切です。さらにステップアップするなら、QuerydslやSpecificationと組み合わせて、複雑な検索条件とページングを両立させる方法も学んでみると面白いですよ。」
生徒
「検索条件との組み合わせ……難しそうですが、やりがいはありそうです。まずは今回のPageableとSortを自分のプロジェクトに組み込んで、使い倒してみます!」
先生
「その意気です!もし実装途中で詰まったら、いつでも聞いてくださいね。Spring Bootの機能をフル活用して、スマートなAPIを完成させましょう!」