- Spring
Spring Data JPAページング|Pageable・Slice・ソート
Spring Data JPA でページングするには、リポジトリのメソッドの引数に Pageable を足し、戻り値を Page<T> か Slice<T> にします。 コントローラーでも Pageable を引数にすると、?page=0&size=20&sort=createdAt,desc のようなクエリパラメータから自動で作られます。
public interface OrderRepository extends JpaRepository<Order, Long> {
Page<Order> findByStatus(OrderStatus status, Pageable pageable);
}
@RestController
class OrderController {
private final OrderRepository repo;
OrderController(OrderRepository repo) {
this.repo = repo;
}
@GetMapping("/api/orders")
PagedModel<OrderDto> list(@RequestParam OrderStatus status,
@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) {
return new PagedModel<>(repo.findByStatus(status, pageable).map(OrderDto::from));
}
}
| パラメータ | 意味 | 既定値(Boot 4.1.1) |
|---|---|---|
page |
ページ番号(0 始まり) | 0 |
size |
1ページの件数 | 20 |
sort |
項目名,asc または 項目名,desc。複数指定できる |
なし(並び順の指定なし) |
| 上限 | size の最大値 |
2000(超えると 2000 に切り詰め) |
確認環境:Spring Boot 4.1.1(Spring Data 2026.0.1 = Spring Data Commons 4.1.1・Spring Data JPA 4.1.1、Spring Framework 7.0.9、Jackson 3.1.5 は Boot の依存管理の版)、OpenJDK 25.0.4.1(Linux)。PageableHandlerMethodArgumentResolver(コントローラーの Pageable を作るクラス)と PageImpl・SliceImpl・PagedModel は、GitHub のタグのソースからビルドして実際に動かし、この記事の表の結果と JSON を取りました。count クエリを省く条件、countQuery、警告文、Boot の設定キーは、Spring Data JPA 4.1.1・Spring Data Commons 4.1.1・Spring Boot v4.1.1 のソースとリファレンスで確かめています(確認日 2026年10月13日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Spring Data JPAページング|Pageable・Slice・ソート」
Pageable・Page・Slice の関係
| 型 | 役割 | 持っている情報 |
|---|---|---|
Pageable |
「何ページ目を、何件ずつ、どの順で」という要求 | ページ番号・件数・並び順 |
PageRequest |
Pageable を自分で作るクラス |
PageRequest.of(0, 20, Sort.by("createdAt").descending()) |
Page<T> |
結果+全体の件数 | 中身・総件数・総ページ数・次があるか |
Slice<T> |
結果+次があるかだけ | 中身・次があるか(総件数は無い) |
List<T> |
結果だけ | 中身(並び順と件数の指定だけ効く) |
// サービスやバッチで自分で作る
Pageable p = PageRequest.of(0, 50, Sort.by(Sort.Order.desc("createdAt"), Sort.Order.asc("id")));
Page<Order> page = repo.findByStatus(OrderStatus.NEW, p);
Page と Slice の違い(count クエリ)
Page は、総件数を知るために中身とは別に count のクエリを実行します。 Slice は count を実行しない代わりに、size + 1 件を取得して「次があるか」だけを判断します(Spring Data JPA 4.1.1 の JpaQueryExecution.SlicedExecution は setMaxResults(pageSize + 1))。
| 比べる点 | Page |
Slice |
|---|---|---|
| 実行する SQL | 中身+count(条件しだいで省かれる) |
中身だけ(size + 1 件) |
| 総件数・総ページ数 | 分かる | 分からない |
| 次があるか | 分かる | 分かる |
| 向いている画面 | ページ番号を並べる一覧(1 2 3 … 10) | 「もっと見る」・無限スクロール |
| 大きな表での速さ | count が重くなりやすい | count が無いぶん軽い |
count が省かれる条件
Spring Data Commons 4.1.1 の PageableExecutionUtils.getPage は、次のときに count クエリを実行しません。
| 条件 | 総件数の決め方 |
|---|---|
1ページ目で、取れた件数が size より少ない |
取れた件数がそのまま総件数 |
2ページ目以降で、取れた件数が1件以上かつ size より少ない(最後のページ) |
offset + 取れた件数 |
| それ以外(ページがいっぱい・空のページ) | count クエリを実行 |
件数が少ない一覧では、count の SQL がログに出ないことがあります。 これは上の最適化によるものです。
PageImpl を動かして、メタデータの値を確かめました。
作った Page |
結果 |
|---|---|
中身3件・page=1・size=3・総件数10 |
totalPages=4・hasNext=true・isLast=false・number=1 |
中身1件・page=3・size=3・総件数10(最後) |
totalPages=4・hasNext=false |
中身0件・page=10・size=3・総件数10(範囲外) |
content=[]・totalPages=4(エラーにはならない) |
SliceImpl:中身3件・page=0・size=3・次あり |
hasNext=true・number=0・size=3 |
範囲外のページを指定しても、空の中身が返るだけでエラーにはなりません。 画面で「該当なし」と区別したいときは、page.getNumber() >= page.getTotalPages() で判断します。
コントローラーで受け取る page・size・sort
Spring Data Commons 4.1.1 の PageableHandlerMethodArgumentResolver に、いろいろなクエリ文字列を渡した結果です(Boot の既定と同じ設定)。
| クエリ文字列 | 作られた Pageable |
|---|---|
| (なし) | page=0 size=20 sort=UNSORTED |
?page=2&size=30 |
page=2 size=30 |
?page=-1 |
page=0(負の数は 0 に) |
?size=0・?size=-5 |
size=20(既定値に戻る) |
?size=5000 |
size=2000(上限で切り詰め) |
?page=abc&size=xyz |
page=0 size=20(エラーにならず既定値) |
?page=99999999999 |
page=0(int に入らない値も既定値) |
?sort=name |
name: ASC(向きの既定は昇順) |
?sort=name,desc |
name: DESC |
?sort=name,desc&sort=id,asc |
name: DESC, id: ASC(複数は sort を繰り返す) |
?sort=name,desc,id |
name: ASC, desc: ASC, id: ASC(向きは最後にだけ書ける) |
?sort=name,ignorecase |
name: ASC, ignoring case |
?sort=,desc |
UNSORTED(項目名が空なら無視) |
page・size に数字以外が来ても 400 にはならず、既定値で動きます。 入力の誤りを利用者に知らせたいなら、@RequestParam int page のように自分で受けて @Min・@Max で検査し、PageRequest.of で作ります。
sort=name,desc,id は「name と id を降順」ではありません。 向き(asc・desc)は最後の1つだけが向きとして読まれ、途中の desc は項目名として扱われます。項目ごとに向きを変えるときは sort を繰り返します。
@PageableDefault・@SortDefault
@GetMapping("/api/orders")
Page<OrderDto> list(@PageableDefault(size = 50, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { … }
| クエリ文字列 | 結果(上の @PageableDefault) |
|---|---|
| (なし) | page=0 size=50 sort=createdAt: DESC |
?page=1 |
page=1 size=50 sort=createdAt: DESC |
?sort=name |
page=0 size=50 sort=name: ASC(sort を送ると既定の並び順は使われない) |
並び順を複数決めたいときは @SortDefault.SortDefaults を使います。
Boot の設定(spring.data.web.*)
Boot 4.1.1 の DataWebProperties の設定キーと既定値です。
| 設定キー | 既定値 | 意味 |
|---|---|---|
spring.data.web.pageable.default-page-size |
20 |
size が無いときの件数 |
spring.data.web.pageable.max-page-size |
2000 |
size の上限 |
spring.data.web.pageable.one-indexed-parameters |
false |
true で page を1始まりにする |
spring.data.web.pageable.page-parameter |
page |
ページ番号のパラメータ名 |
spring.data.web.pageable.size-parameter |
size |
件数のパラメータ名 |
spring.data.web.pageable.prefix |
空 | パラメータ名の前に付ける文字 |
spring.data.web.pageable.qualifier-delimiter |
_ |
@Qualifier 付きで2つ受けるときの区切り |
spring.data.web.pageable.serialization-mode |
direct |
Page の JSON の形(下の PagedModel の節) |
spring.data.web.sort.sort-parameter |
sort |
並び順のパラメータ名 |
spring:
data:
web:
pageable:
max-page-size: 100 # 1回に取れる件数を絞る
one-indexed-parameters: true
serialization-mode: via-dto
max-page-size は、既定の 2000 から業務に合う値まで下げておくと安全です。 ?size=2000 を何度も送られると、DB とメモリに負荷がかかります。setMaxPageSize(100) にしたクラスで ?size=500 を試すと、size=100 に切り詰められました。
ページ番号を1始まりにする
spring:
data:
web:
pageable:
one-indexed-parameters: true
| クエリ文字列 | 作られた Pageable(1始まりの設定) |
|---|---|
| (なし) | page=0(1ページ目) |
?page=1 |
page=0(1ページ目) |
?page=2 |
page=1(2ページ目) |
?page=0 |
page=0(1ページ目として扱う) |
この設定が変えるのは「受け取る page」だけです。 Page#getNumber() や JSON の number は 0 始まりのままです。画面に出すときは number + 1 にします。
@Query でページングする(countQuery)
public interface OrderRepository extends JpaRepository<Order, Long> {
// JPQL:count クエリは自動で作られる
@Query("select o from Order o where o.customer.id = :customerId")
Page<Order> findByCustomer(@Param("customerId") long customerId, Pageable pageable);
// JOIN や GROUP BY を含むときは count を明示する
@Query(value = "select o from Order o join o.items i where i.product.category = :cat",
countQuery = "select count(distinct o) from Order o join o.items i where i.product.category = :cat")
Page<Order> findByCategory(@Param("cat") String category, Pageable pageable);
// ネイティブ SQL
@NativeQuery(value = "SELECT * FROM orders WHERE status = ?1",
countQuery = "SELECT count(*) FROM orders WHERE status = ?1")
Page<Order> findNativeByStatus(String status, Pageable pageable);
}
Spring Data JPA 4.1.1 のリファレンスには、次のように書かれています。
| 書いてあること | 意味 |
|---|---|
HQL(Hibernate)と EQL(EclipseLink)は専用のパーサーで、並び順の追加と COUNT の自動作成をする |
JPQL は多くの場合 countQuery が要らない |
| SQL(ネイティブ)の標準の仕組みは単純なクエリだけ対応 | ネイティブは countQuery を書くのが確実 |
複雑なネイティブ SQL は JSqlParser を classpath に置くか countQuery を書く |
JSqlParser があればネイティブにも使われる |
@QueryHints(forCounting = false) で count にはヒントを付けない |
count だけ別の扱いにできる |
countQuery を書くときは、本体と同じ条件にします。 条件がずれると、総件数と中身が合わなくなります。JOIN で行が増えるときは count(distinct o) にします。
並び順と JpaSort.unsafe
sort パラメータは、そのまま ORDER BY に使われます。Spring Data JPA 4.1.1 のソースでは、項目名以外(関数など)が入ると次の例外になります。
Sort expression '…' must only contain property references or aliases used in the select clause; If you really want to use something other than that for sorting, please use JpaSort.unsafe(…)
存在しない項目名を指定すると、Spring Data Commons の PropertyReferenceException になります。文の形は No property 'nmae' found for type 'Order' で、近い名前があれば ; Did you mean 'name' が続きます(ソースの文面)。この例外は何もしなければ 500 になります。 利用者が sort を自由に送れる API では、並べ替えてよい項目を決めておき、それ以外は 400 にします。
private static final Set<String> SORTABLE = Set.of("createdAt", "amount", "id");
static Pageable checkSort(Pageable pageable) {
for (Sort.Order o : pageable.getSort()) {
if (!SORTABLE.contains(o.getProperty())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "sort に指定できない項目です: " + o.getProperty());
}
}
return pageable;
}
並び順を必ず一意にする
createdAt だけで並べると、同じ時刻の行の順番が毎回変わり、ページをまたいで同じ行が2回出たり、出なかったりします。 最後に主キー(id)を足して、順番を一意にします。
Sort sort = Sort.by(Sort.Order.desc("createdAt"), Sort.Order.asc("id"));
Sort.by(Sort.Order.desc("createdAt"), Sort.Order.asc("id").ignoreCase()) の結果は createdAt: DESC,id: ASC, ignoring case、Sort.by("name").descending().and(Sort.by("id")) は name: DESC,id: ASC でした。
Spring Data Commons 4.1 では、メソッド参照で項目を指定する TypedPropertyPath が加わり、Sort.by(TypedPropertyPath...) が使えます(ソースの @since 4.1)。文字列の項目名の打ち間違いをコンパイル時に防げます。
JSON で返す:PagedModel と警告文
Page をそのままコントローラーから返すと、起動後の最初の JSON 変換で次の WARN が出ます(Spring Data Commons 4.1.1 の SpringDataJackson3Configuration の文)。
Serializing PageImpl instances as-is is not supported, meaning that there is no guarantee about the stability of the resulting JSON structure!
For a stable JSON structure, please use Spring Data's PagedModel (globally via @EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO))
or Spring HATEOAS and Spring Data's PagedResourcesAssembler as documented in https://docs.spring.io/spring-data/commons/reference/repositories/core-extensions.html#core.web.pageables.
PageImpl の JSON の形は保証されておらず、Spring Data の版が上がると変わることがある、という警告です。 Jackson 3.1.5 で、同じ Page(中身3件・page=1・size=3・総件数10)を2通りで JSON にしました。
new PagedModel<>(page) の JSON(安定した形):
{
"content" : [ "a", "b", "c" ],
"page" : {
"size" : 3,
"number" : 1,
"totalElements" : 10,
"totalPages" : 4
}
}
PageImpl をそのまま JSON にした形(DIRECT):
{
"content" : [ "a", "b", "c" ],
"empty" : false,
"first" : false,
"last" : false,
"number" : 1,
"numberOfElements" : 3,
"pageable" : {
"offset" : 3,
"pageNumber" : 1,
"pageSize" : 3,
"paged" : true,
"sort" : {
"empty" : true,
"sorted" : false,
"unsorted" : true
},
"unpaged" : false
},
"size" : 3,
"sort" : {
"empty" : true,
"sorted" : false,
"unsorted" : true
},
"totalElements" : 10,
"totalPages" : 4
}
| 方法 | 書き方 | 警告 |
|---|---|---|
| コントローラーで包む | return new PagedModel<>(page); |
出ない |
| 全体の設定で包む(Boot) | spring.data.web.pageable.serialization-mode=via-dto |
出ない |
| 全体の設定で包む(Boot を使わない) | @EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO) |
出ない |
| そのまま返す(既定) | return page; |
出る |
PagedModel(org.springframework.data.web.PagedModel)は Spring Data 3.3 で加わりました(@EnableSpringDataWebSupport の pageSerializationMode の @since 3.3)。既定が DIRECT のままなのは、これまでの JSON の形を変えないためとソースの Javadoc に書かれています。すでに PageImpl の形で画面を作っているなら、via-dto に切り替えると JSON の形が変わります(totalPages などが page の下に入る)。フロントと合わせて切り替えます。
Spring HATEOAS を使うなら、PagedResourcesAssembler で次・前のページのリンク付きの JSON にもできます。REST API 全体の作りは関連記事「Spring BootでREST APIを作る|入力チェック・エラー処理・テストまで」で扱っています。
大きな表のページング
page が大きくなるほど、OFFSET 方式は遅くなります。 DB は OFFSET の分の行を読み飛ばすためです。数十万件を超える表で後ろのページまで読ませるなら、次の方法があります。
| 方法 | 内容 |
|---|---|
Slice にする |
count を無くす |
| キーセット(カーソル)方式 | 「前のページの最後の値より後」を条件にする。Spring Data の ScrollPosition.keyset() と Window<T> |
| count を軽くする | countQuery で JOIN を減らす。総件数を「1000件以上」で打ち切る画面にする |
OFFSET 方式とカーソル方式の違いは関連記事「ページネーションのoffsetとcursorの違い|100万件で実測比較」で扱っています。コレクションを JOIN FETCH するクエリとページングの組み合わせは、関連記事「JPAのN+1問題の解決|JOIN FETCH等4手法の比較とコード全文」で扱っています。
テストで確かめる
@DataJpaTest
class OrderRepositoryTest {
@Autowired OrderRepository repo;
@Test
void ページ2を新しい順に取る() {
for (int i = 0; i < 25; i++) repo.save(new Order(OrderStatus.NEW, LocalDateTime.of(2026, 10, 1, 0, 0).plusHours(i)));
Page<Order> page = repo.findByStatus(OrderStatus.NEW,
PageRequest.of(1, 10, Sort.by(Sort.Order.desc("createdAt"), Sort.Order.asc("id"))));
assertThat(page.getTotalElements()).isEqualTo(25);
assertThat(page.getTotalPages()).isEqualTo(3);
assertThat(page.getContent()).hasSize(10);
assertThat(page.hasNext()).isTrue();
}
}
コントローラーのテストでは、MockMvc で ?page=1&size=10&sort=createdAt,desc を送り、jsonPath("$.page.number").value(1) のように確かめます(PagedModel の形の場合)。テストの書き方は関連記事「Spring Bootテスト入門|@WebMvcTestとMockMvcの使い分け」で扱っています。
よくある質問
Q. page=1 を送ったのに2ページ目が返ります。
A. page は既定で 0 始まりです。1始まりで送りたいなら spring.data.web.pageable.one-indexed-parameters=true にします。
Q. size=10000 を送っても 2000 件しか返りません。
A. max-page-size の既定が 2000 のためです。全件が要る処理は、ページングの API ではなく、CSV の出力などの別の仕組みにします。
Q. 総件数が要らない画面でも count が遅いです。
A. 戻り値を Slice<T> に変えると count は実行されません。hasNext() で「次へ」ボタンを出せます。
Q. Pageable を受けるメソッドに Sort も渡したいです。
A. Pageable が並び順を持っているため、並び順は Pageable の中に入れます。Sort を別に渡すと、どちらが使われるかが読み手に分かりにくくなります。リポジトリの引数は Pageable だけにします。
まとめ
- リポジトリは
Pageableを引数に、Page<T>・Slice<T>を戻り値に pageは 0 始まり、sizeの既定は 20、上限は 2000。数字以外はエラーにならず既定値- 複数の並び順は
sortを繰り返す。向きは最後にだけ書ける Pageは count を実行(最後のページなどでは省かれる)。総件数が要らなければSlice- JOIN・ネイティブ SQL は
countQueryを書く - JSON は
PagedModel(serialization-mode=via-dto)で返すと警告が消え、形が安定する - 並び順は最後に
idを足して一意に。利用者のsortは許可した項目だけ
参考・出典
確認日はいずれも 2026年10月13日です。
- Spring Data Commons リファレンス「Spring Data Extensions」(Web support):https://docs.spring.io/spring-data/commons/reference/repositories/core-extensions.html
- Spring Data Commons リファレンス「Defining Query Methods」(Paging, Iterating Large Results, Sorting & Limiting):https://docs.spring.io/spring-data/commons/reference/repositories/query-methods-details.html
- Spring Data JPA リファレンス「JPA Query Methods」:https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html
- Spring Data Commons ソースコード(4.1.1)PageableHandlerMethodArgumentResolverSupport:https://github.com/spring-projects/spring-data-commons/blob/4.1.1/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverSupport.java
- Spring Data Commons ソースコード(4.1.1)SpringDataJackson3Configuration:https://github.com/spring-projects/spring-data-commons/blob/4.1.1/src/main/java/org/springframework/data/web/config/SpringDataJackson3Configuration.java
- Spring Data Commons ソースコード(4.1.1)PageableExecutionUtils:https://github.com/spring-projects/spring-data-commons/blob/4.1.1/src/main/java/org/springframework/data/support/PageableExecutionUtils.java
- Spring Data JPA ソースコード(4.1.1)JpaQueryExecution:https://github.com/spring-projects/spring-data-jpa/blob/4.1.1/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java
- Spring Boot ソースコード(v4.1.1)DataWebProperties:https://github.com/spring-projects/spring-boot/blob/v4.1.1/module/spring-boot-data-commons/src/main/java/org/springframework/boot/data/autoconfigure/web/DataWebProperties.java
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Spring Data JPAページング|Pageable・Slice・ソート」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。