# 4.5. ページネーション

## 4.5.1. Overview

本章では、検索条件に一致するデータをページ分割して表示する方法(ページネーション)について説明する。

検索条件に一致するデータが大量になる場合は、ページネーション機能を使用することを推奨する。
一度に大量のデータを取得し画面に表示すると、以下3点の問題が発生する可能性がある。

- サーバ側のメモリ枯渇の発生。
  単発のリクエストで問題が発生しなくても、同時に複数実行された場合に`java.lang.OutOfMemoryError`が発生する可能性がある。
- ネットワーク負荷の発生。
  不要なデータがネットワークに流れることで、ネットワーク全体にかかる負荷が高くなり、システム全体のレスポンスタイムに影響を与える可能性がある。
- 画面のレスポンス遅延の発生。
  大量のデータを扱う場合、サーバの処理、ネットワークのトラフィック処理、クライアントの描画処理の全てで時間がかかるため、画面のレスポンスが遅くなる可能性がある。

### 4.5.1.1. ページ分割時の一覧画面の表示について

ページネーション機能を利用してページ分割した場合、以下のような画面になる。
各要素の表示にはサーバ側で行う検索処理をページ検索に対応させる必要がある。ページ検索機能については、「ページ検索について」を参照されたい。
また、各要素の概要及びHTML構造等については、「ページネーションの表示について」を参照されたい。

項番 | 説明
--- | ---
(1) | ページ検索処理で取得したデータを表示する。
(2) | ページを移動するためのリンクを表示する。 リンク押下時には、該当ページを表示するためのリクエストを送信する。
(3) | ページネーションに関連する情報(合計件数、合計ページ数、表示ページ数など)を表示する。

### 4.5.1.2. ページ検索について

ページネーションを実現する際には、まずサーバ側で行う検索処理をページ検索できるように実装する必要がある。
本ガイドラインでは、サーバ側のページ検索は、Spring Dataから提供されている仕組みを利用することを前提としている。

#### 4.5.1.2.1. Spring Data提供のページ検索機能について

Spring Dataより提供されているページ検索用の機能は、以下の通り。

項番 | 説明
--- | ---
1 | リクエストパラメータよりページ検索に必要な情報(検索対象のページ位置、取得件数、ソート条件)を抽出し、抽出した情報を`org.springframework.data.domain.Pageable`のオブジェクトとしてControllerの引数に引き渡す。 この機能は、`org.springframework.data.web.PageableHandlerMethodArgumentResolver`クラスとして提供されており、`spring-mvc.xml`の`<mvc:argument-resolvers>`要素に追加することで有効となる。 リクエストパラメータについては、「Note欄」を参照されたい。
2 | ページ情報(合計件数、該当ページのデータ、検索対象のページ位置、取得件数、ソート条件)を保持する。 この機能は、`org.springframework.data.domain.Page`インタフェースとして提供されており、デフォルトの実装クラスとして`org.springframework.data.domain.PageImpl`が提供されている。 JSP 共通ライブラリより提供しているページネーションリンクを出力するためのJSPタグライブラリを使用し、Pageオブジェクトから必要なデータを取得する。 Thymeleaf ページネーションリンクを出力する際には、`Page`オブジェクトから必要なデータを取得する。

Note

ページ検索用のリクエストパラメータについて

Spring Dataより提供されているページ検索用のリクエストパラメータは以下の3つとなる。

> 項番 | パラメータ名 | 説明
> --- | --- | ---
> | page | 検索対象のページ位置を指定するためのリクエストパラメータ。 値には、0以上の数値を指定する。 デフォルトの設定では、ページ位置の値は”`0`“ から開始する。そのため、1ページ目のデータを取得する場合は”`0`“を、2ページ目のデータを取得する場合は”`1`“を指定する必要がある。
> | size | 取得する件数を指定するためのリクエストパラメータ。 値には、1以上の数値を指定する。 `PageableHandlerMethodArgumentResolver`の`maxPageSize`に指定された値より大きい値が指定された場合は、`maxPageSize`の値が`size`の値となる。
> | sort | ソート条件を指定するためのパラメータ(複数指定可能)。 値には、`{ソート項目名(,ソート順)}`の形式で指定する。 ソート順には、`ASC`又は`DESC`のどちらかの値を指定し、省略した場合は`ASC`が適用される。 項目名は”`,`“区切りで複数指定することが可能である。 例えば、クエリ文字列として`sort=lastModifiedDate,id,DESC&sort=subId`が指定された場合、`ORDER BY lastModifiedDate DESC, id DESC, subId ASC`のようなOrder By句をQueryに追加することになる。

### 4.5.1.3. ページネーションの表示について

「ページ分割時の一覧画面の表示について」にて説明した画面の各要素について説明する。

#### 4.5.1.3.1. 取得データの表示について

ページ検索処理で検索条件(検索対象のページ位置、取得件数、ソート条件等)を指定して取得したデータを表示する。
ページ検索については、「ページ検索について」を参照されたい。

#### 4.5.1.3.2. ページネーションリンクの表示について

この章で実装例として取り上げるページネーションリンクについて以下の流れで説明する。

1. ページネーションリンクの構成

2. ページネーションリンクのHTML構造

3. JSPタグライブラリのパラメータについて

なおこの章では、TERASOLUNA共通ライブラリで提供されるJSPタグである`<t:pagination>`のデフォルト設定で出力されるHTMLを例に、ページネーションリンクの実装例を説明する。
実装例のページネーションリンクの構成・レイアウト等は、あくまでも一例である。実装例を参考にアプリケーションの要件によって適宜変更すること。
以降の説明で使用する画面は、Bootstrap v3.2.0のスタイルシートを適用している。

##### 4.5.1.3.2.1. ページネーションリンクの構成

ページネーションリンクは、以下の要素から構成される。

項番 | 説明
--- | ---
(1) | 最初のページに移動するためのリンク。
(2) | 前のページに移動するためのリンク。
(3) | 指定したページに移動するためのリンク。
(4) | 次のページに移動するためのリンク。
(5) | 最後のページに移動するためのリンク。

ページネーションリンクは、以下の状態をもつ。

項番 | 説明
--- | ---
(6) | 現在表示しているページで操作することができないリンクであることを示す状態。 具体的には、1ページ目を表示している時の「最初のページに移動するためのリンク」「前のページに移動するためのリンク」と、最終ページを表示している時の「次のページに移動するためのリンク」「最後のページに移動するためのリンク」がこの状態となる。 この状態を`disabled`と定義する。
(7) | 現在表示しているページであることを示す状態。 この状態を`active`と定義する。

上記を実現するHTMLは、以下の構造となる。
図中の番号は、上記で説明した「ページネーションリンクの構成」と「ページネーションリンクの状態」の項番に対応させている。

    <t:pagination page="${page}" />

Todo

ThymeleafのDialectは次版以降に作成予定となる。

- 出力されるHTML

##### 4.5.1.3.2.2. ページネーションリンクのHTML構造

ページネーションリンクのHTMLは、以下の構造となる。

TERASOLUNA共通ライブラリで提供しているJSPタグを使用する。

- HTML

- 画面イメージ

  項番 | 説明 | デフォルト値
  --- | --- | ---
  (1) | ページネーションリンクの構成要素をまとめるための要素。 共通ライブラリでは、この部分を「Outer Element」と呼び、複数の「Inner Element」を保持する。 使用する要素は、JSPタグライブラリのパラメータによって変更することが出来る。 | `<ul>`要素
  (2) | 「Outer Element」のスタイルクラスを指定するための属性。 共通ライブラリでは、この部分を「Outer Element Class」と呼び、属性値はJSPタグライブラリのパラメータによって指定する。 | 指定なし
  (3) | ページネーションリンクを構成するための要素。 共通ライブラリでは、この部分を「Inner Element」と呼び、ページ移動するためのリクエストを送信するための`<a>`要素を保持する。 使用する要素は、JSPタグライブラリのパラメータによって変更することが出来る。 | `<li>`要素
  (4) | 「Inner Element」のスタイルクラスを指定するための属性。 共通ライブラリでは、この部分を「Inner Element Class」と呼び、属性値は表示しているページ位置によってJSPタグライブラリ内の処理で切り替わる。 | 「Note欄」を参照されたい。
  (5) | ページ移動するためのリクエストを送信するための要素。 共通ライブラリでは、この部分を「Page Link」と呼ぶ。 | `<a>`要素固定
  (6) | ページ移動するためのURLを指定するための属性。 共通ライブラリでは、この部分を「Page Link URL」と呼ぶ。 | 下記の「Note欄」を参照されたい。
  (7) | ページ移動するためのリンクの表示テキストを指定する。 共通ライブラリでは、この部分を「Page Link Text」と呼ぶ。 | 下記の「Note欄」を参照されたい。

  Note

  「Inner Element」の数について

  デフォルトの設定では、「Inner Element」は最大で14個となる。内訳は以下の通り。

  - 最初のページに移動するためのリンク : 1

  - 前のページに移動するためのリンク : 1

  - 指定したページに移動するためのリンク : 最大10

  - 次のページに移動するためのリンク : 1

  - 最後のページに移動するためのリンク : 1

  「Inner Element」の数は、JSPタグライブラリのパラメータの指定によって変更することができる。

> Note

> 「Inner Element Class」の設定値について

> デフォルトの設定では、ページ位置によって、以下3つの値となる。

> - `disabled` : 現在表示しているページでは操作することができないリンクであることを示すためのスタイルクラス。

> - `active` : 現在表示しているページのリンクであることを示すためのスタイルクラス。

> - 指定なし : 上記以外のリンクであることを示す。

> `disabled`と`active`は、JSPタグライブラリのパラメータの指定によって別の値に変更することができる。

> Note

> 「Page Link URL」のデフォルト値について

> リンクの状態が`disabled`と`active`の場合は`javascript:void(0)`、それ以外の場合は`?page={page}&size={size}`となる。

> 「Page Link URL」は、JSPタグライブラリのパラメータの指定によって別の値に変更することができる。

> Note

> 「Page Link Text」のデフォルト値について

> > 項番 | リンク名 | デフォルト値
> > --- | --- | ---
> > | 最初のページに移動するためのリンク | `<<`
> > | 前のページに移動するためのリンク | `<`
> > | 指定したページに移動するためのリンク | 該当ページのページ番号 (変更不可)
> > | 次のページに移動するためのリンク | `>`
> > | 最後のページに移動するためのリンク | `>>`

> 「指定したページに移動するためのリンク」以外は、JSPタグライブラリのパラメータの指定によって、別の値に変更することができる。

スタイルクラスの指定は省略している。スタイルクラスは、作成するアプリケーションの要件に応じて適宜指定すること。

- HTML

- 画面イメージ

  項番 | 説明
  --- | ---
  (1) | ページネーションリンクの構成要素をまとめるための要素。
  (2) | ページネーションリンクを構成するための要素。
  (3) | ページ移動するためのリクエストを送信するための要素。
  (4) | ページ移動するためのURLを指定するための属性。
  (5) | ページ移動するためのリンクの表示テキストを指定する。

##### 4.5.1.3.2.3. JSPタグライブラリのパラメータについて

JSPタグライブラリのパラメータに値を指定することで、デフォルト動作を変更することができる。

以下にパラメータの一覧を示す。

レイアウトを制御するためのパラメータ

項番 | パラメータ名 | 説明
--- | --- | ---
| outerElement | 「Outer Element」として使用するHTML要素名を指定する。 例) div
| outerElementClass | 「Outer Element」のclass属性に設定するスタイルシートのクラス名を指定する。 例) pagination
| innerElement | 「Inner Element」として使用するHTML要素名を指定する。 例) span
| innerElementClass | 「Inner Element」のclass属性に設定するスタイルシートのクラス名を指定する。5.6.0から追加 例) enablePage
| disabledClass | `disabled`状態と判断された「Inner Element」のclass属性に設定する値を指定する。 例) hiddenPage
| activeClass | `active`状態の「Inner Element」のclass属性に設定する値を指定する。 例) currentPage
| anchorClass | 「Page Link」のclass属性に設定するスタイルシートのクラス名を指定する。5.7.0から追加 例) pageLink
| firstLinkText | 「最初のページに移動するためのリンク」の「Page Link Text」に設定する値を指定する。 `""`を指定すると、「最初のページに移動するためのリンク」自体が出力されなくなる。 例) First
| previousLinkText | 「前のページに移動するためのリンク」の「Page Link Text」に設定する値を指定する。 `""`を指定すると、「前のページに移動するためのリンク」自体が出力されなくなる。 例) Prev
| nextLinkText | 「次のページに移動するためのリンク」の「Page Link Text」に設定する値を指定する。 `""`を指定すると、「次のページに移動するためのリンク」自体が出力されなくなる。 例) Next
| lastLinkText | 「最後のページに移動するためのリンク」の「Page Link Text」に設定する値を指定する。 `""`を指定すると、「次のページに移動するためのリンク」自体が出力されなくなる。 例) Last
| maxDisplayCount | 「指定したページに移動するためのリンク」の最大表示数を指定する。 “`0`“ を指定すると、「指定したページに移動するためのリンク」自体が出力されなくなる。 例) 5

レイアウトを制御するためのパラメータを、全てデフォルトから変更した時に出力されるHTMLは以下の通り。
図中の番号は、上記で説明したパラメータ一覧の項番に対応している。

- JSP

      <t:pagination page="${page}"
          outerElement="div"
          outerElementClass="pagination"
          innerElement="span"
          innerElementClass="enablePage"
          disabledClass="hiddenPage"
          activeClass="currentPage"
          anchorClass="pageLink"
          firstLinkText="First"
          previousLinkText="Prev"
          nextLinkText="Next"
          lastLinkText="Last"
          maxDisplayCount="5"
          />

- 出力されるHTML

動作を制御するためのパラメータ

項番 | パラメータ名 | 説明
--- | --- | ---
| disabledHref | `disabled`状態のリンクの「Page Link URL」に設定する値を指定する。
| pathTmpl | 「Page Link URL」に設定するリクエストパスのテンプレートを指定する。 ページ表示時のリクエストパスとページ移動するためのリクエストパスが異なる場合は、このパラメータにページ移動用のリクエストパスを指定する必要がある。 指定するリクエストパスのテンプレートには、ページ位置(page)や取得件数(size)などをパス変数(プレースホルダ)として指定することができる。 指定した値はUTF-8でURLエンコーディングされる。
| queryTmpl | 「Page Link URL」のクエリ文字列のテンプレートを指定する。 ページ移動する際に必要となるページネーション用のクエリ文字列(page,size,sortパラメータ)を生成するためのテンプレートを指定する。 ページ位置や取得件数のリクエストパラメータ名をデフォルト以外の値にする場合は、このパラメータにクエリ文字列を指定する必要がある。 指定するクエリ文字列のテンプレートには、ページ位置(page)や取得件数(size)などをパス変数(プレースホルダ)として指定することができる。 指定した値はUTF-8でURLエンコーディングされる。 この属性は、ページネーション用のクエリ文字列(page,size,sortパラメータ)を生成するための属性であるため、検索条件を引き継ぐためのクエリ文字列はcriteriaQuery属性に指定すること。
| criteriaQuery | 「Page Link URL」に追加する検索条件用のクエリ文字列を指定する。 「Page Link URL」に検索条件を引き継ぐ場合は、このパラメータに検索条件用のクエリ文字列を指定すること。 指定した値はURLエンコーディングされないため、URLエンコーディング済みのクエリ文字列を指定する必要がある。 フォームオブジェクトに格納されている検索条件をURLエンコーディング済みのクエリ文字列に変換する場合は、共通ライブラリから提供しているELファンクション(`f:query(Object)`)を使用すると、簡単に条件を引き継ぐことができる。
| disableHtmlEscapeOfCriteriaQuery | `criteriaQuery`パラメータに指定された値に対するHTMLエスケープ処理を無効化するためのフラグ。 `true`を指定する事で、`criteriaQuery`パラメータに指定された値に対してHTMLエスケープ処理が行われなくなる。(デフォルト値は`false`) trueを指定する場合は、XSS対策が必要な文字がクエリ文字列内に含まれない事が保証されていること。
| enableLinkOfCurrentPage | `active`状態のページリンクを押下した際に、該当ページを再表示するためのリクエストを送信するためのフラグ。 `true`を指定する事で、「Page Link URL」に該当ページを再表示するためのURL(デフォルト値は`?page={page}&size={size}`)が設定される。(デフォルト値は`false`で、「Page Link URL」には`disabledHref`属性の値が設定される)

Note

disabledHrefの設定値について

デフォルトでは、`disabledHref`属性には`javascript:void(0)`が設定されている。ページリンク押下時の動作を無効化するだけであれば、デフォルトのままでよい。

ただし、デフォルトの状態でページリンクにフォーカスを移動又はマウスオーバーした場合、ブラウザのステータスバーに`javascript:void(0)`が表示されることがある。この挙動を変えたい場合は、JavaScriptを使用してページリンク押下時の動作を無効化する必要がある。実装例については、「JavaScriptを使用したページリンクの無効化」を参照されたい。

Note

パス変数(プレースホルダ)について

`pathTmpl`及び`queryTmpl`に指定できるパス変数は、以下の通り。

> 項番 | パス変数名 | 説明
> --- | --- | ---
> | page | ページ位置を埋め込むためのパス変数。
> | size | 取得件数を埋め込むためのパス変数。
> | sortOrderProperty | ソート条件のソート項目を埋め込むためのパス変数。
> | sortOrderDirection | ソート条件のソート順を埋め込むためのパス変数。

パス変数は、`{パス変数名}`の形式で指定する。

Warning

ソート条件の制約事項

ソート条件のパス変数に設定される値は、ひとつのソート条件のみとなっている。そのため、複数のソート条件を指定して検索した結果を、ページネーション表示する必要がある場合は、共通ライブラリから提供しているJSPタグライブラリを拡張する必要がある。

動作を制御するためのパラメータを変更した時に出力されるHTMLは、以下の通り。
図中の番号は、上記で説明したパラメータ一覧の項番に対応している。

- JSP

      <t:pagination page="${page}"
          disabledHref="#"
          pathTmpl="${pageContext.request.contextPath}/article/list/{page}/{size}"
          queryTmpl="sort={sortOrderProperty},{sortOrderDirection}"
          criteriaQuery="${f:query(articleSearchCriteriaForm)}"
          enableLinkOfCurrentPage="true" />

- 出力されるHTML

#### 4.5.1.3.3. ページネーション情報の表示について

ページネーションに関する情報の表示を行う。
Spring Data提供のページ検索機能を使用することで、以下の情報を画面に表示することができる。

- 合計件数

- 検索対象のページ位置

- 取得件数

- ソート条件

ページ検索については、「ページ検索について」を参照されたい。

### 4.5.1.4. ページネーション機能使用時の処理フロー

Spring Dataより提供されているページネーション機能を利用した際の処理フローは、以下の通り。
(JSPの場合は共通ライブラリから提供しているJSPタグライブラリを利用する)

項番 | 説明
--- | ---
(1) | 検索条件と共に、リクエストパラメータとして検索対象のページ位置(page)と取得件数(size)を指定してリクエストを送信する。
(2) | `PageableHandlerMethodArgumentResolver`は、リクエストパラメータに指定されている検索対象のページ位置(page)と取得件数(size)を取得し、`Pageable`オブジェクトを生成する。 生成された`Pageable`オブジェクトは、Controllerのハンドラメソッドの引数に設定される。
(3) | Controllerは、引数で受け取った`Pageable`オブジェクトを、Serviceのメソッドに引き渡す。
(4) | Serviceは、引数で受け取った`Pageable`オブジェクトを、RepositoryのQueryメソッドに引き渡す。
(5) | Repositoryは、検索条件に一致するデータの合計件数(totalElements)と、引数で受け取った`Pageable`オブジェクトに指定されているページ位置(page)と取得件数(size)の範囲に存在するデータを、データベースより取得する。
(6) | Repositoryは、取得した合計件数(totalElements)、取得データ(content)、引数で受け取った`Pageable`オブジェクトより`Page`オブジェクトを作成し、Service及びControllerへ返却する。
(7) | Controllerは、返却された`Page`オブジェクトを、`Model`オブジェクトに格納後、JSPに遷移する。
(8) | JSPは、`Model`オブジェクトに格納されている`Page`オブジェクトを取得し、共通ライブラリから提供されているページネーション用のJSPタグライブラリ(`<t:pagination>`)を呼び出す。 ページネーション用のJSPタグライブラリは`Page`オブジェクトを参照し、ページネーションリンクを生成する。
(9) | JSPで生成したHTMLを、クライアント(ブラウザ)に返却する。
(10) | ページネーションリンクを押下すると、該当ページを表示するためリクエストが送信される。

項番 | 説明
--- | ---
(1) | 検索条件と共に、リクエストパラメータとして検索対象のページ位置(page)と取得件数(size)を指定してリクエストを送信する。
(2) | `PageableHandlerMethodArgumentResolver`は、リクエストパラメータに指定されている検索対象のページ位置(page)と取得件数(size)を取得し、`Pageable`オブジェクトを生成する。 生成された`Pageable`オブジェクトは、Controllerのハンドラメソッドの引数に設定される。
(3) | Controllerは、引数で受け取った`Pageable`オブジェクトを、Serviceのメソッドに引き渡す。
(4) | Serviceは、引数で受け取った`Pageable`オブジェクトを、RepositoryのQueryメソッドに引き渡す。
(5) | Repositoryは、検索条件に一致するデータの合計件数(totalElements)と、引数で受け取った`Pageable`オブジェクトに指定されているページ位置(page)と取得件数(size)の範囲に存在するデータを、データベースより取得する。
(6) | Repositoryは、取得した合計件数(totalElements)、取得データ(content)、引数で受け取った`Pageable`オブジェクトより`Page`オブジェクトを作成し、Service及びControllerへ返却する。
(7) | Controllerは、返却された`Page`オブジェクトを、`Model`オブジェクトに格納後、ThymeleafのテンプレートHTMLに遷移する。 テンプレートHTMLは、`Model`オブジェクトに格納されている`Page`オブジェクトを取得し、ページネーションリンクを生成する。
(8) | 生成したHTMLを、クライアント(ブラウザ)に返却する。
(9) | ページネーションリンクを押下すると、該当ページを表示するためリクエストが送信される。

Note

Repositoryの実装について

上記フローの(5)と(6)の具体的な実装例については、

- データベースアクセス（MyBatis3編）

を参照されたい。

## 4.5.2. How to use

ページネーション機能の具体的な使用方法を以下に示す。

### 4.5.2.1. アプリケーションの設定

#### 4.5.2.1.1. Spring Dataのページネーション機能を有効化するための設定

リクエストパラメータに指定された検索対象のページ位置(page)、取得件数(size)、ソート条件(sort)を、`Pageable`オブジェクトとしてControllerの引数に設定するための機能を有効化する。
下記の設定は、ブランクプロジェクトでは設定済みの状態になっている。

`SpringMvcConfig.java`

    @EnableAspectJAutoProxy
    @EnableWebMvc
    @Configuration
    public class SpringMvcConfig implements WebMvcConfigurer {

        @Override
        public void addArgumentResolvers(
                List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(pageableHandlerMethodArgumentResolver()); // (1)
        }

        @Bean
        public PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver() {
            return new PageableHandlerMethodArgumentResolver();
        }

項番 | 説明
--- | ---
(1) | `addArgumentResolvers`で`argumentResolvers`に`org.springframework.data.web.PageableHandlerMethodArgumentResolver`を追加する。 `PageableHandlerMethodArgumentResolver`で指定できるプロパティについては、「PageableHandlerMethodArgumentResolverのプロパティ値について」を参照されたい。

`spring-mvc.xml`

    <mvc:annotation-driven>
        <mvc:argument-resolvers>
            <!-- (1) -->
            <bean
                class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
        </mvc:argument-resolvers>
    </mvc:annotation-driven>

項番 | 説明
--- | ---
(1) | `<mvc:argument-resolvers>`に`org.springframework.data.web.PageableHandlerMethodArgumentResolver`を指定する。 `PageableHandlerMethodArgumentResolver`で指定できるプロパティについては、「PageableHandlerMethodArgumentResolverのプロパティ値について」を参照されたい。

### 4.5.2.2. ページ検索の実装

ページ検索を実現するための実装方法を以下に示す。

#### 4.5.2.2.1. アプリケーション層の実装

ページ検索に必要な情報(検索対象のページ位置、取得件数、ソート条件)を、Controllerの引数として受け取り、Serviceのメソッドに引き渡す。

- Controller

      @GetMapping("list")
      public String list(@Validated ArticleSearchCriteriaForm form,
              BindingResult result,
              Pageable pageable, // (1)
              Model model) {

          ArticleSearchCriteria criteria = beanMapper.map(form); // (2)

          Page<Article> page = articleService.searchArticle(criteria, pageable); // (3)

          model.addAttribute("page", page); // (4)

          return "article/list";
      }

  項番 | 説明
  --- | ---
  (1) | ハンドラメソッドの引数として`Pageable`を指定する。 `Pageable`オブジェクトには、ページ検索に必要な情報(検索対象のページ位置、取得件数、ソート条件)が格納されている。
  (2) | BeanのマッピングにはMapStructを用いて作成したマッパーインタフェースを使用する。 Mapperインタフェースの定義方法についてはBeanマッピング(MapStruct)を参照されたい。
  (3) | Serviceのメソッドの引数に`Pageable`オブジェクトを指定して呼び出す。
  (4) | Serviceから返却された検索結果(`Page`オブジェクト )を`Model`に追加する。`Model`に追加することで、View(JSP/テンプレートHTML)から参照できるようになる。

  Note

  リクエストパラメータにページ検索に必要な情報の指定がない場合の動作について

  ページ検索に必要な情報(検索対象のページ位置、取得件数、ソート条件)がリクエストパラメータに指定されていない場合は、デフォルト値が適用される。 デフォルト値は、以下の通り。

  - 検索対象のページ位置 : `0`(1ページ目)

  - 取得件数 : `20`

  - ソート条件 : `null`(ソート条件なし)

  デフォルト値は、以下の２つの方法で変更することができる。

  - ハンドラメソッドの`Pageable`の引数に、`@org.springframework.data.web.PageableDefault`アノテーションを指定してデフォルト値を定義する。

  - `PageableHandlerMethodArgumentResolver`の`fallbackPageable`プロパティにデフォルト値を定義した`Pageable`オブジェクトを指定する。

`@PageableDefault`アノテーションを使用してデフォルト値を指定する方法について説明する。
ページ検索処理毎にデフォルト値を変更する必要がある場合は、`@PageableDefault`アノテーションを使ってデフォルト値を指定する。

    @GetMapping("list")
    public String list(@Validated ArticleSearchCriteriaForm form,
            BindingResult result,
            @PageableDefault( // (1)
                    page = 0,    // (2)
                    size = 50,   // (3)
                    direction = Direction.DESC,  // (4)
                    sort = {     // (5)
                        "publishedDate",
                        "articleId"
                        }
                    ) Pageable pageable,
            Model model) {
        // ...
        return "article/list";
    }

項番 | 説明 | デフォルト値
--- | --- | ---
(1) | `Pageable`の引数に`@PageableDefault`アノテーションを指定する。 | -
(2) | ページ位置のデフォルト値を変更する場合は、`@PageableDefault`のpage属性に値を指定する。 通常変更する必要はない。 | “`0`“ (1ページ目)
(3) | 取得件数のデフォルト値を変更する場合は、`@PageableDefault`のsize又はvalue属性に値を指定する。 | `10`
(4) | ソート条件のデフォルト値を変更する場合は、`@PageableDefault`のdirection属性に値を指定する。 | `Direction.ASC` (昇順)
(5) | ソート条件のソート項目を指定する場合は、`@PageableDefault`のsort属性にソート項目を指定する。 複数の項目でソートする場合は、ソートするプロパティ名を配列で指定する。 上記例のように設定した場合、`ORDER BY publishedDate DESC, articleId DESC`のようなソート条件がQueryに追加される。 | 空の配列 (ソート項目なし)

Tip

@PageableDefaultアノテーションで指定できるソート順について

`@PageableDefault`アノテーションで指定できるソート順は昇順か降順のどちらか一つなので、項目ごとに異なるソート順を指定したい場合は`@org.springframework.data.web.SortDefaults`アノテーションを使用する必要がある。

具体的には、`ORDER BY publishedDate DESC, articleId ASC`のようなソート順にしたい場合である。

Tip

取得件数のデフォルト値のみ変更する場合の指定方法

取得件数のデフォルト値のみ変更する場合は、`@PageableDefault(50)`と指定することもできる。これは`@PageableDefault(size = 50)`と同じ動作となる。

`@SortDefaults`アノテーションを使用してデフォルト値を指定する方法について説明する。
`@SortDefaults`アノテーションは、ソート項目が複数あり、項目ごとに異なるソート順を指定したい場合に使用する。

    @RequestMapping("list")
    public String list(
            @Validated ArticleSearchCriteriaForm form,
            BindingResult result,
            @PageableDefault(size = 50)
            @SortDefaults(  // (1)
                    {
                        @SortDefault(  // (2)
                                     sort = "publishedDate",    // (3)
                                     direction = Direction.DESC // (4)
                                    ),
                        @SortDefault(
                                     sort = "articleId"
                                    )
                    }) Pageable pageable,
            Model model) {
        // ...
        return "article/list";
    }

項番 | 説明 | デフォルト値
--- | --- | ---
(1) | `Pageable`の引数に`@SortDefaults`アノテーションを指定する。 `@SortDefaults`アノテーションには、複数の`@org.springframework.data.web.SortDefault`アノテーションを配列として指定することができる。 | -
(2) | `@SortDefaults`アノテーションの value属性に、`@SortDefault`アノテーションを指定する。 複数指定する場合は配列として指定する。 | -
(3) | `@PageableDefault`のsort又はvalue属性にソート項目を指定する。 複数の項目を指定する場合は配列として指定する。 | 空の配列 (ソート項目なし)
(4) | ソート条件のデフォルト値を変更する場合は、`@PageableDefault` のdirection属性に値を指定する。 | `Direction.ASC` (昇順)

Tip

ソート項目のデフォルト値のみ指定する場合の指定方法

取得項目のみ指定する場合は、`@PageableDefault("articleId")`と指定することもできる。これは`@PageableDefault(sort = "articleId")`や`@PageableDefault(sort = "articleId", direction = Direction.ASC)`と同じ動作となる。

アプリケーション全体のデフォルト値を変更する必要がある場合は、`spring-mvc.xml`に定義した`PageableHandlerMethodArgumentResolver`の`fallbackPageable`プロパティにデフォルト値を定義した`Pageable`オブジェクトを指定する。

`fallbackPageable`の説明や具体的な設定例については、「PageableHandlerMethodArgumentResolverのプロパティ値について」を参照されたい。

#### 4.5.2.2.2. ドメイン層の実装(MyBatis3編)

MyBatisを使用してデータベースにアクセスする場合は、Controllerから受け取った`Pageable`オブジェクトより、必要な情報を抜き出してRepositoryに引き渡す。
該当データを抽出するためのSQLやソート条件については、SQLマッピングで実装する必要がある。

ドメイン層で実装するページ検索処理の詳細については、

- Entityのページネーション検索(MyBatis3標準方式)

- Entityのページネーション検索(SQL絞り込み方式)

- Entityのページネーション検索(検索結果のソート)

を参照されたい。

### 4.5.2.3. ページネーションの実装

ページ検索処理で取得した`Page`オブジェクトを一覧画面に表示し、ページネーションリンク及びページネーション情報(合計件数、合計ページ数、表示ページ数など)を表示する方法を以下に示す。

#### 4.5.2.3.1. 基本実装(JSP)

##### 4.5.2.3.1.1. 取得データの表示

ページ検索処理で取得したデータを表示するための実装例を以下に示す。

- Controller

      @GetMapping("list")
      public String list(@Validated ArticleSearchCriteriaForm form, BindingResult result,
              Pageable pageable, Model model) {

          if (!StringUtils.hasLength(form.getWord())) {
              return "article/list";
          }

          ArticleSearchCriteria criteria = beanMapper.map(form);

          Page<Article> page = articleService.searchArticle(criteria, pageable);

          model.addAttribute("page", page); // (1)

          return "article/list";
      }

  項番 | 説明
  --- | ---
  (1) | `page`という属性名で`Page`オブジェクトを`Model`に格納する。 JSPでは`page`という属性名を指定して`Page`オブジェクトにアクセスすることになる。

- JSP

      <%-- ... --%>

      <%-- (1) --%>
      <c:when test="${page != null && page.totalPages != 0}">

          <table class="maintable">
              <thead>
                  <tr>
                      <th class="no">No</th>
                      <th class="articleClass">Class</th>
                      <th class="title">Title</th>
                      <th class="overview">Overview</th>
                      <th class="date">Published Date</th>
                  </tr>
              </thead>

              <%-- (2) --%>
              <c:forEach var="article" items="${page.content}" varStatus="rowStatus">
                  <tr>
                      <td class="no">
                          ${(page.number * page.size) + rowStatus.count}
                      </td>
                      <td class="articleClass">
                          ${f:h(article.articleClass.name)}
                      </td>
                      <td class="title">
                          ${f:h(article.title)}
                      </td>
                      <td class="overview">
                          ${f:h(article.overview)}
                      </td>
                      <td class="date">
                          ${f:h(article.publishedDate)}
                      </td>
                  </tr>
              </c:forEach>

          </table>

          <div class="paginationPart">

          <%-- ... --%>

          </div>
      </c:when>

      <%-- ... --%>

  項番 | 説明
  --- | ---
  (1) | 上記例では、条件に一致するデータが存在するかチェックを行い、一致するデータがない場合はヘッダ行も含めて表示していない。 一致するデータがない場合でもヘッダ行は表示させる必要がある場合は、この分岐は不要となる。
  (2) | JSTLの`<c:forEach>`タグを使用して、取得したデータの一覧を表示する。 取得したデータは、`Page`オブジェクトの`content`プロパティにリスト形式で格納されている。

- 上記JSPで出力される画面例

##### 4.5.2.3.1.2. ページネーションリンクの表示

ページ移動するためのリンク(ページネーションリンク)を表示するための実装例を以下に示す。

共通ライブラリより提供しているJSPタグライブラリを使用して、ページネーションリンクを出力する。

- `include.jsp`

  共通ライブラリより提供しているJSPタグライブラリの宣言を行う。ブランクプロジェクトでは設定済みの状態となっている。

      <%@ taglib uri="http://terasoluna.org/tags" prefix="t"%>       <%-- (1) --%>
      <%@ taglib uri="http://terasoluna.org/functions" prefix="f"%>  <%-- (2) --%>

  項番 | 説明
  --- | ---
  (1) | ページネーションリンクを表示するためのJSPタグが格納されている。
  (2) | ページネーションリンクを使う際に利用するJSPのELファンクションが格納されている。

- JSP

      <t:pagination page="${page}" /> <%-- (3) --%>

  項番 | 説明
  --- | ---
  (3) | `<t:pagination>`タグを使用する。 page属性には、Controllerで`Model`に格納した`Page`オブジェクトを指定する。

- 出力されるHTML

  下記の出力例は、`?page=0&size=6`を指定して検索した際の結果である。

      <ul>
          <li class="disabled"><a href="javascript:void(0)">&lt;&lt;</a></li>
          <li class="disabled"><a href="javascript:void(0)">&lt;</a></li>
          <li class="active"><a href="javascript:void(0)">1</a></li>
          <li><a href="?page=1&size=6">2</a></li>
          <li><a href="?page=2&size=6">3</a></li>
          <li><a href="?page=3&size=6">4</a></li>
          <li><a href="?page=4&size=6">5</a></li>
          <li><a href="?page=5&size=6">6</a></li>
          <li><a href="?page=6&size=6">7</a></li>
          <li><a href="?page=7&size=6">8</a></li>
          <li><a href="?page=8&size=6">9</a></li>
          <li><a href="?page=9&size=6">10</a></li>
          <li><a href="?page=1&size=6">&gt;</a></li>
          <li><a href="?page=9&size=6">&gt;&gt;</a></li>
      </ul>

ページネーションリンク用のスタイルシートを用意しないと以下のような表示となる。
見てわかる通り、ページネーションリンクとして成立していない。
ページネーションリンクとして成立する最低限のスタイルシートの定義の追加と、JSPの変更を行うと、以下のような表示となる。

- 画面イメージ

- JSP

      <%-- ... --%>

      <t:pagination page="${page}"
          outerElementClass="pagination" /> <%-- (4) --%>

      <%-- ... --%>

  項番 | 説明
  --- | ---
  (4) | ページネーションリンクであることを示すクラス名を指定する。 クラス名を指定することによってスタイルシートで指定するスタイルの適用範囲をページネーションリンクに限定することができる。

- スタイルシート

      .pagination li {
          display: inline;
      }

      .pagination li>a {
          margin-left: 10px;
      }

ページネーションリンクとして成立したが、以下2つの問題が残る。

- 押下できるリンクと押下できないリンクの区別ができない。

- 現在表示しているページ位置がわからない。

上記の問題を解決する手段として、Bootstrap v3.2.0のスタイルシートを適用すると、以下のような表示となる。

- 画面イメージ

- スタイルシート

  bootstrapのcssファイルは、webjarsで提供されているものを使用する。
  以下、ページネーション関連のスタイル定義の抜粋。

      .pagination {
        display:inline-block;
        padding-left:0;
        margin:20px 0;
        border-radius:4px
      }
      .pagination>li {
        display:inline
      }
      .pagination>li > a,.pagination>li > span {
        position:relative;
        float:left;
        padding:6px 12px;
        margin-left:-1px;
        line-height:1.42857143;
        color:#428bca;
        text-decoration:none;
        background-color:#fff;
        border:1px solid #ddd
      }
      .pagination > li:first-child > a,
      .pagination > li:first-child > span {
        margin-left:0;
        border-top-left-radius:4px;
        border-bottom-left-radius:4px
      }
      .pagination > li:last-child > a,
      .pagination > li:last-child > span {
        border-top-right-radius:4px;
        border-bottom-right-radius:4px
      }
      .pagination > li > a:hover,
      .pagination > li > span:hover,
      .pagination > li > a:focus,
      .pagination > li > span:focus {
        color:#2a6496;
        background-color:#eee;
        border-color:#ddd
      }
      .pagination > .active > a,
      .pagination > .active > span,
      .pagination > .active > a:hover,
      .pagination > .active > span:hover,
      .pagination > .active > a:focus,
      .pagination > .active > span:focus {
        z-index:2;
        color:#fff;
        cursor:default;
        background-color:#428bca;
        border-color:#428bca
      }
      .pagination > .disabled > span,
      .pagination > .disabled > span:hover,
      .pagination > .disabled > span:focus,
      .pagination > .disabled > a,
      .pagination > .disabled > a:hover,
      .pagination > .disabled > a:focus {
        color:#777;
        cursor:not-allowed;
        background-color:#fff;
        border-color:#ddd
      }
      .pagination-lg > li > a,
      .pagination-lg > li > span {
        padding:10px 16px;
        font-size:18px
      }
      .pagination-lg > li:first-child > a,
      .pagination-lg > li:first-child > span {
        border-top-left-radius:6px;
        border-bottom-left-radius:6px
      }
      .pagination-lg > li:last-child > a,
      .pagination-lg > li:last-child > span {
        border-top-right-radius:6px;
        border-bottom-right-radius:6px
      }
      .pagination-sm > li > a,
      .pagination-sm > li > span {
        padding:5px 10px;
        font-size:12px
      }
      .pagination-sm > li:first-child > a,
      .pagination-sm > li:first-child > span {
        border-top-left-radius:3px;
        border-bottom-left-radius:3px
      }
      .pagination-sm > li:last-child > a,
      .pagination-sm > li:last-child > span {
        border-top-right-radius:3px;
        border-bottom-right-radius:3px
      }

- JSP

  JSPでは配置したcssファイルを読み込む定義を追加する。

      <link rel="stylesheet"
          href="${pageContext.request.contextPath}/webjars/bootstrap/css/bootstrap.min.css"
          type="text/css" media="screen, projection">

##### 4.5.2.3.1.3. ページネーション情報の表示

ページネーションに関連する情報(合計件数、合計ページ数、表示ページ数など)を表示するための実装例を以下に示す。

- 画面例

- JSP

      <div>
          <fmt:formatNumber value="${page.totalElements}" /> results <%-- (1) --%>
      </div>
      <div>
          ${f:h(page.number + 1) } /       <%-- (2) --%>
          ${f:h(page.totalPages)} Pages    <%-- (3) --%>
      </div>

  項番 | 説明
  --- | ---
  (1) | 検索条件に一致するデータの合計件数を表示する場合は、`Page`オブジェクトの`totalElements`プロパティから値を取得する。
  (2) | 表示しているページのページ数を表示する場合は、`Page`オブジェクトの`number`プロパティから値を取得し、`+1`する。 `Page`オブジェクトの`number`プロパティは”`0`“ 開始のため、ページ番号を表示する際は`+1`が必要となる。
  (3) | 検索条件に一致するデータの合計ページ数を表示する場合は、`Page`オブジェクトの`totalPages`プロパティから値を取得する。

該当ページの表示データ範囲を表示するための実装例を以下に示す。

- 画面例

- JSP

      <div>
          <%-- (4) --%>
          <fmt:formatNumber value="${(page.number * page.size) + 1}" /> -
          <%-- (5) --%>
          <fmt:formatNumber value="${(page.number * page.size) + page.numberOfElements}" />
      </div>

  項番 | 説明
  --- | ---
  (4) | 開始位置を表示する場合は、`Page`オブジェクトの`number`プロパティと`size`プロパティを使って計算する。 `Page`オブジェクトの`number`プロパティは”`0`“開始のため、データ開始位置を表示する際は`+1`が必要となる。
  (5) | 終了位置を表示する場合は、`Page`オブジェクトの`number`プロパティ、`size`プロパティ、`numberOfElements`プロパティ を使って計算する。 最終ページは端数となる可能性があるので、`numberOfElements`を加算する必要がある。

  Tip

  数値のフォーマットについて

  表示する数値をフォーマットする必要がある場合は、JSTLから提供されているタグライブラリ(`<fmt:formatNumber>`)を使用する。

##### 4.5.2.3.1.4. ページリンクで検索条件を引き継ぐ

検索条件をページ移動時のリクエストに引き継ぐ方法を、以下に示す。

- JSP

      <%-- (1) --%>
      <div id="criteriaPart">
          <form:form action="${pageContext.request.contextPath}/article/list" method="get"
              modelAttribute="articleSearchCriteriaForm">
              <form:input path="word" />
              <form:button>Search</form:button>
              <br>
          </form:form>
      </div>

      <%-- ... --%>

      <t:pagination page="${page}"
          outerElementClass="pagination"
          criteriaQuery="${f:query(articleSearchCriteriaForm)}" /> <%-- (2) --%>

  項番 | 説明
  --- | ---
  (1) | 検索条件を指定するフォーム。 検索条件として`word`が存在する。
  (2) | ページ移動時のリクエストに検索条件を引き継ぐ場合は、`criteriaQuery`属性にURLエンコーディング済みのクエリ文字列を指定する。 検索条件をフォームオブジェクトに格納する場合は、共通ライブラリから提供しているELファンクション(`f:query(Object)`) を使用すると、簡単に条件を引き継ぐことができる。 上記例の場合、`?page=ページ位置&size=取得件数&word=入力値`という形式のクエリ文字列が生成される。

  Note

  f:query(Object) の仕様について

  `f:query`の引数には、フォームオブジェクトなどのJavaBeanと`Map`オブジェクトを指定することができる。JavaBeanの場合はプロパティ名がリクエストパラメータ名となり、`Map`オブジェクトの場合はマップのキー名がリクエストパラメータとなる。生成されるクエリ文字列は、UTF-8のURLエンコーディングが行われる。

  terasoluna-gfw-web 5.0.1.RELEASEより、ネスト構造をもつJavaBeanと`Map`オブジェクトを指定できるように改善されている。

  `f:query`の詳細な仕様(URLエンコーディングの仕様など)については、「f:query()」を参照されたい。

  Warning

  f:queryを使用して生成したクエリ文字列をqueryTmpl属性に指定した際の動作について

  `f:query`を使用して生成したクエリ文字列をqueryTmpl属性に指定すると、URLエンコーディングが重複してしまい、特殊文字の引き継ぎが正しく行われないことが判明している。

  URLエンコーディングが重複してしまう事象については、terasoluna-gfw-web 1.0.1.RELEASE以上で利用可能な`criteriaQuery`属性を使用することで回避する事が出来る。

##### 4.5.2.3.1.5. ページリンクでソート条件を引き継ぐ

ソート条件をページ移動時のリクエストに引き継ぐ方法を、以下に示す。

- JSP

      <t:pagination page="${page}"
          outerElementClass="pagination"
          queryTmpl="page={page}&size={size}&sort={sortOrderProperty},{sortOrderDirection}" />  <%-- (1) --%>

  項番 | 説明
  --- | ---
  (1) | ページ移動時のリクエストにソート条件を引き継ぐ場合は、`queryTmpl`を指定し、クエリ文字列にソート条件を追加する。 ソート条件を指定するためのパラメータの仕様については、「ページ検索用のリクエストパラメータについて」を参照されたい。 上記例の場合、`?page=0&size=20&sort=ソート項目,ソート順(ASC or DESC)`がクエリ文字列となる。

#### 4.5.2.3.2. レイアウト変更(JSP)

##### 4.5.2.3.2.1. 先頭ページと最終ページに移動するリンクの削除

「最初のページに移動するためのリンク」と「最後のページに移動するためのリンク」を削除するための実装例を、以下に示す。

- 画面例

- JSP

      <t:pagination page="${page}"
          outerElementClass="pagination"
          firstLinkText=""
          lastLinkText="" /> <%-- (1) (2) --%>

  項番 | 説明
  --- | ---
  (1) | 「最初のページに移動するためのリンク」を非表示にする場合は、`<t:pagination>`タグの`firstLinkText`属性に`""`を指定する。
  (2) | 「最後のページに移動するためのリンク」を非表示にする場合は、`<t:pagination>`タグの`lastLinkText`属性に`""`を指定する。

##### 4.5.2.3.2.2. 前ページと次ページに移動するリンクの削除

「前のページに移動するためのリンク」と「次のページに移動するためのリンク」を削除するための実装例を、以下に示す。

- 画面例

- JSP

      <t:pagination page="${page}"
          outerElementClass="pagination"
          previousLinkText=""
          nextLinkText="" /> <%-- (1) (2) --%>

  項番 | 説明
  --- | ---
  (1) | 「前のページに移動するためのリンク」を非表示にする場合は、`<t:pagination>`タグの previousLinkText属性に`""`を指定する。
  (2) | 「次のページに移動するためのリンク」を非表示にする場合は、`<t:pagination>`タグの nextLinkText属性に`""`を指定する。

##### 4.5.2.3.2.3. disabled状態のリンクの削除

`disabled`状態のリンクを削除するための実装例を、以下に示す。
`disabled`時のスタイルシートに、以下の定義を追加する。

- 画面例

- スタイルシート

      .pagination .disabled {
          display: none;  /* (1) */
      }

  項番 | 説明
  --- | ---
  (1) | `disabled`クラスの属性値として、`display: none;`を指定する。

##### 4.5.2.3.2.4. 指定ページへ移動するリンクの最大表示数の変更

指定したページに移動するためのリンクの最大表示数を変更するための実装例を、以下に示す。

- 画面例

- JSP

      <t:pagination page="${page}"
          outerElementClass="pagination"
          maxDisplayCount="5" /> <%-- (1) --%>

  項番 | 説明
  --- | ---
  (1) | 指定したページに移動するためのリンクの最大表示数を変更する場合は、`<t:pagination>`タグの`maxDisplayCount`属性に値を指定する。

##### 4.5.2.3.2.5. 指定ページへ移動するリンクの削除

指定したページに移動するためのリンクを削除するための実装例を、以下に示す。

- 画面例

- JSP

      <t:pagination page="${page}"
          outerElementClass="pagination"
          maxDisplayCount="0" /> <%-- (1) --%>

  項番 | 説明
  --- | ---
  (1) | 指定したページに移動するためのリンクを非表示にする場合は、`<t:pagination>`タグの`maxDisplayCount`属性に”`0`“を指定する。

#### 4.5.2.3.3. 動作変更(JSP)

##### 4.5.2.3.3.1. ソート条件の指定

クライアントからソート条件を指定するための実装例を、以下に示す。

- 画面例

- JSP

      <div id="criteriaPart">
          <form:form
              action="${pageContext.request.contextPath}/article/search"
              method="get" modelAttribute="articleSearchCriteriaForm">
              <form:input path="word" />
              <%-- (1) --%>
              <form:select path="sort">
                  <form:option value="publishedDate,DESC">Newest</form:option>
                  <form:option value="publishedDate,ASC">Oldest</form:option>
              </form:select>
              <form:button>Search</form:button>
              <br>
          </form:form>
      </div>

  項番 | 説明
  --- | ---
  (1) | クライアントからソート条件を指定する場合は、ソート条件を指定するためのパラメータを追加する。 ソート条件を指定するためのパラメータの仕様については、「ページ検索用のリクエストパラメータについて」を参照されたい。 上記例では、publishedDateの昇順と降順をプルダウンで選択できるようにしている。

##### 4.5.2.3.3.2. JavaScriptを使用したページリンクの無効化

デフォルトでは、`disabled`状態と`active`状態のページリンク押下時の動作を無効化するために、`<t:pagination>`タグの`disabledHref`属性に`javascript:void(0)`を設定している。 この状態でページリンクにフォーカスを移動又はマウスオーバーすると、ブラウザのステータスバーに`javascript:void(0)`が表示されることがある。 この挙動を変えたい場合は、JavaScriptを使用してページリンク押下時の動作を無効化する必要がある。

以下に実装例を示す。

JSP

    <%-- (1) --%>
    <script type="text/javascript" src="${pageContext.request.contextPath}/webjars/jquery/jquery.min.js"></script>

    <%-- (2) --%>
    <script type="text/javascript">
        $(function(){
            $(document).on("click", ".disabled a, .active a", function(){
                return false;
            });
        });
    </script>

    <%-- ... --%>

    <%-- (3) --%>
    <t:pagination page="${page}" disabledHref="#" />

項番 | 説明
--- | ---
(1) | jQueryのjsファイルを読み込む。 上記例では、JavaScriptを使用してページリンク押下時の動作を無効化するためにjQueryのAPIを利用する。
(2) | jQueryのAPIを使用して、`disabled`状態と`active`状態のページリンクのクリックイベントを無効化する。 ただし、`<t:pagination>`タグの`enableLinkOfCurrentPage`属性に`true`を指定している場合は、`active`状態のページリンクのクリックイベントを無効化してはいけない。
(3) | `disabledHref`属性に”`#`“を指定する。

### 4.5.2.4. Thymeleafの実装

#### 4.5.2.4.1. テンプレートHTMLの実装

ページ検索処理で取得した`Page`オブジェクトからデータを取得し、ページネーションを行う画面に取得したデータ、ページネーションリンク、ページネーションに関する情報(合計件数、合計ページ数、表示ページ数など)を表示する方法について説明する。

ページネーションリンクやページネーション情報の表示は、アプリケーション内で共通的に使用されるため部品化することを推奨する。
また、ページネーションリンクの出力範囲や該当ページの表示データ範囲の計算等の複雑な計算ロジックはテンプレートHTMLではなくJavaで実装することを推奨する。

そのため、ここでは以下の構成でテンプレートHTMLの実装を行う例を示す。

成果物の構成要素 | 説明
--- | ---
テンプレートHTML（ページネーションを行う画面） | 取得したデータの一覧の表示を実装する
テンプレートHTML（フラグメント） | すべてのテンプレートHTML（ページネーションを行う画面）で同様の、ページネーションリンクやページネーション情報の表示の実装を共通化する
式オブジェクト | ページネーションリンクの出力範囲や該当ページの表示データ範囲の計算ロジックを実装する

なお、「アプリケーション層の実装」の例では、`Page`オブジェクトを`page`という名前で`Model`に格納している。
そのため、テンプレートHTMLの実装では`page`という名前を指定して`Page`オブジェクトにアクセスすることができる。

##### 4.5.2.4.1.1. 取得データの表示

ページ検索処理で取得したデータを表示するための実装例を以下に示す。 データの表示内容は画面ごとに異なるため、共通化せずテンプレートHTML（ページネーションを行う画面）に実装すると良い。

- テンプレートHTML（ページネーションを行う画面）

      <!--/* ... */-->

      <!--/* (1) */-->
      <div th:if="${page} != null" th:remove="tag">

        <table class="maintable">

          <thead>
            <tr>
              <th>No</th>
              <th>Class</th>
              <th>Title</th>
              <th>Overview</th>
              <th>Published Date</th>
            </tr>
          </thead>

          <!--/* (2) */-->
          <tbody>
            <tr th:each="article, status : ${page.content}" th:object="${article}">
              <td class="no" th:text="*{articleId}"></td>
              <td class="articleClass" th:text="*{articleClass.name}"></td>
              <td class="title" th:text="*{title}"></td>
              <td class="overview" th:text="*{overview}"></td>
              <td class="date"
                th:text="*{#dates.format(publishDate, 'yyyy/MM/dd HH:mm:ss')}"></td>
            </tr>
          </tbody>

        </table>

      </div>

      <!--/* ... */-->

  項番 | 説明
  --- | ---
  (1) | 上記例では、条件に一致するデータが存在するかチェックを行い、一致するデータがない場合はヘッダ行も含めて表示していない。 一致するデータがない場合でもヘッダ行は表示させる必要がある場合は、この分岐は不要となる
  (2) | `th:each`属性を使用して、取得したデータの一覧を表示する。 取得したデータは、`Page`オブジェクトの`content`プロパティにリスト形式で格納されている。

- 上記テンプレートHTMLで出力される画面例

##### 4.5.2.4.1.2. ページネーションリンクの表示

ページ移動するためのリンク(ページネーションリンク)は、「ページネーションリンクの構成」にて説明した以下の5つのパーツに分けて出力を行う。

- 最初のページに移動するためのリンク

- 前のページに移動するためのリンク

- 指定したページに移動するためのリンク

- 次のページに移動するためのリンク

- 最後のページに移動するためのリンク

「指定したページに移動するためのリンク」の出力では、表示するリンクの範囲(開始位置、終了位置)を計算し出力数を制御する必要があり、複雑な計算ロジックを実装することになるため、テンプレートHTMLではなくJavaで実装することを推奨する。
ここでは、表示するリンクの範囲(開始位置、終了位置)の計算を式オブジェクトに実装する方法を実装例として採用する。
また、ページネーションリンクはアプリケーション内で共通的に使用されるため、ページネーションリンクを生成するテンプレートHTMLをフラグメントとして定義する例を実装例として採用する。
そのため、以下の流れで「ページネーションリンクの表示」の実装例を説明する。

1. リンクの出力範囲を計算する式オブジェクトを実装する

2. フラグメントにページネーションリンクのHTMLを実装する

3. ページネーションを行う画面にフラグメントを利用してページネーションリンクを表示する

Note

指定したページに移動するためのリンクの出力範囲の計算の実装方法について

この章で説明している式オブジェクトを使用した実装方法はあくまでも一例であり、実装方法を規定するものではない。他に以下のような実装方法があるため、プロジェクトの開発方針に則り実装していただきたい。

- `Page`オブジェクトを拡張し、表示するリンクの範囲(開始位置、終了位置)を計算、保持する。

- ControllerのHelperクラスでリンクの範囲(開始位置、終了位置)を計算する。

###### 4.5.2.4.1.2.1. リンクの出力範囲を計算する式オブジェクトを実装する

「指定したページに移動するためのリンク」の出力では、表示するリンクの範囲(開始位置、終了位置)を計算し出力数を制御する必要があり、複雑な計算ロジックを実装することになるため、テンプレートHTMLではなくJavaで実装することを推奨する。
ここでは、表示するリンクの範囲(開始位置、終了位置)を計算するメソッドを持つ式オブジェクトを実装し、カスタムダイアレクトを追加する方法について説明する。
カスタムダイアレクトの追加の詳細については、「カスタムダイアレクトの追加」を参照されたい。
追加するカスタムダイアレクトの使用例は以下の通りである。

テンプレート記述例

ここでは、Pageオブジェクトとリンクの最大表示数を入力として、ページ番号のリストを出力する`#pageInfo.sequence( Pageオブジェクト, リンクの最大表示数)`メソッドを追加する。これを`th:each`属性の引数として利用することで、指定したページに移動するためのリンクを繰り返し出力する。
なお、以下に示す実装例では操作可否の制御(`disabled`及び`active`)は行っていない。

    <li th:each="i : ${#pageInfo.sequence(page, 5)}">
      <a th:href="@{/sample(page=${i-1},size=${page.size})}" th:text="${i}"></a>
    </li>

独自属性の処理結果

ページサイズが10、ページリンクの出力範囲が1から5までの場合、以下のように出力される。

    <li><a href="/sample?page=0&amp;size=10">1</a></li>
    <li><a href="/sample?page=1&amp;size=10">2</a></li>
    <li><a href="/sample?page=2&amp;size=10">3</a></li>
    <li><a href="/sample?page=3&amp;size=10">4</a></li>
    <li><a href="/sample?page=4&amp;size=10">5</a></li>

まず、式オブジェクトを実装する。実装例を以下に示す。

実装例

    import org.apache.poi.ss.formula.functions.T;
    import org.springframework.data.domain.Page;
    import org.thymeleaf.util.NumberUtils;

    public class PageInfo {

        // (1)
        public Integer[] sequence(Page<T> page, int pageLinkMaxDispNum) {

            // (2)
            int begin = Math.max(1, page.getNumber() + 1 - pageLinkMaxDispNum / 2);
            int end = begin + (pageLinkMaxDispNum - 1);
            if (end > page.getTotalPages() - 1) {
                end = page.getTotalPages();
                begin = Math.max(1, end - (pageLinkMaxDispNum - 1));
            }

            // (3)
            return NumberUtils.sequence(begin, end);
        }
     }

項番 | 説明
--- | ---
(1) | `Page`オブジェクトとリンクの最大表示数を引数に取り、`Integer[]`型を返すメソッドを定義する。
(2) | 表示するリンクの範囲(開始位置、終了位置)を計算する。 `Page`オブジェクトの`number`プロパティは`0`開始のため、ページ番号を表示する際は`+1`が必要となる。
(3) | 計算したリンクの範囲(開始位置、終了位置)のリストを生成し返却する。

次に、実装した式オブジェクトをテンプレートに適用するためにダイアレクトを実装する。実装例を以下に示す。

実装例（式オブジェクトの登録）

    public class PageInfoDialect implements IExpressionObjectDialect {

        // (1)
        private static final String PAGE_INFO_DIALECT_NAME = "pageInfo";
        private static final Set<String> EXPRESSION_OBJECT_NAMES = Collections
                .singleton(PAGE_INFO_DIALECT_NAME);

        @Override
        public IExpressionObjectFactory getExpressionObjectFactory() {
            return new IExpressionObjectFactory() {

                // (1)
                @Override
                public Set<String> getAllExpressionObjectNames() {
                    return EXPRESSION_OBJECT_NAMES;
                }

                // (2)
                @Override
                public Object buildObject(IExpressionContext context,
                        String expressionObjectName) {
                    if (PAGE_INFO_DIALECT_NAME.equals(expressionObjectName)) {
                        return new PageInfo();
                    }
                    return null;
                }

                @Override
                public boolean isCacheable(String expressionObjectName) {
                    return true;
                }

            };
        }

        // omitted

    }

項番 | 説明
--- | ---
(1) | `pageInfo`という名前で式オブジェクトを登録する。
(2) | 実装した式オブジェクトを登録する。

最後に、作成したカスタムダイアレクトの設定を行う。設定例を以下に示す。

spring-mvc.xml

    <bean id="templateEngine" class="org.thymeleaf.spring6.SpringTemplateEngine">

        <!-- omitted -->

        <property name="additionalDialects">
            <set>
                <bean class="org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect" />
                <bean class="com.example.sample.dialect.PageInfoDialect"/> <!-- (1) -->
            </set>
        </property>
    </bean>

項番 | 説明
--- | ---
(1) | テンプレートエンジンに作成したカスタムダイアレクトを追加する。

以上でカスタムダイアレクトの実装及び設定は完了となる。

###### 4.5.2.4.1.2.2. フラグメントにページネーションリンクのHTMLを実装する

ページネーションリンクのテンプレートHTMLのフラグメントの実装例を以下に示す。

以降で説明する実装例は、`th:fragment`属性を利用してページネーションリンクのHTMLを部品化している。
ページネーションリンクは、アプリケーション内で共通的に使用されるため部品化することを推奨する。HTMLの部品化については、「Thymeleafのテンプレートレイアウト機能を使用したHTMLの部品化」を参照されたい。
また、実装例のhtmlに記述している`${requestURI}`は、Thymeleafから`Model`を介して`HttpServletRequest`内の`requestURI`を利用する実装の例である。
ページネーションにおける`requestURI`の利用は、アプリケーション全体で共通的に必要になるケースが多いと考えられるため、ここでは各ハンドラメソッドで個別に`Model`に配置するのではなく、`@ControllerAdvice`を利用してController全体の共通処理として実装する例を示す。
Thymeleafから`HttpServletRequest`の内容を利用する際の注意点については、「Web オブジェクト（HttpServletRequest、HttpSession等）の利用」を参照されたい。

以降、以下のファイル構成を前提に実装例を示す。

- File Path

      WEB-INF
        └─ views
              └─ pgnt
                    ├─ fragment.html     (フラグメントを定義するテンプレートHTML)
                    └─ serchResult.html  (ページネーションを行う画面を実装するテンプレートHTML)

- ControllerAdvice

      @ControllerAdvice
      public class ThymeleafCommonControllerAdvice {
          @ModelAttribute("requestURI") // (1)
          public String requestURI(HttpServletRequest request) {
              return request.getRequestURI();
          }
      }

  項番 | 説明
  --- | ---
  (1) | テンプレートHTML(フラグメント)の`${requestURI}`がアクセスするための`@ModelAttribute`。

- テンプレートHTML(フラグメント)

      <!--/* ... */-->

      <!--/* (1), (2) */-->
      <div th:fragment="pagination (page)" th:object="${page}" th:remove="tag">

        <!--/* (3), (4) */-->
        <ul th:if="*{totalElements} != 0" class="pagination"
          th:with="pageLinkMaxDispNum = 10, disabledHref = 'javascript:void(0)', currentUrl = ${requestURI}">

          <!--/* (5) */-->
          <li th:class="*{isFirst()} ? 'disabled'">
            <!--/* (6) */-->
            <a th:href="*{isFirst()} ? ${disabledHref} : @{{currentUrl}(currentUrl=${currentUrl},page=0,size=*{size})}">&lt;&lt;</a>
          </li>

          <!--/* (7) */-->
          <li th:class="*{isFirst()} ? 'disabled'">
            <a th:href="*{isFirst()} ? ${disabledHref} : @{{currentUrl}(currentUrl=${currentUrl},page=*{number - 1},size=*{size})}">&lt;</a>
          </li>

          <!--/* (8) */-->
          <li th:each="i : ${#pageInfo.sequence(page, pageLinkMaxDispNum)}"
            th:with="isActive=${i} == *{number + 1}" th:class="${isActive} ? 'active'">
            <a th:href="${isActive} ? ${disabledHref} : @{{currentUrl}(currentUrl=${currentUrl},page=${i - 1},size=*{size})}" th:text="${i}"></a>
          </li>

          <!--/* (9) */-->
          <li th:class="*{isLast()} ? 'disabled'">
            <a th:href="*{isLast()} ? ${disabledHref} : @{{currentUrl}(currentUrl=${currentUrl},page=*{number + 1},size=*{size})}">&gt;</a>
          </li>

          <!--/* (10) */-->
          <li th:class="*{isLast()} ? 'disabled'">
            <a th:href="*{isLast()} ? ${disabledHref} : @{{currentUrl}(currentUrl=${currentUrl},page=*{totalPages - 1},size=*{size})}">&gt;&gt;</a>
          </li>

        </ul>

      </div>

      <!--/* ... */-->

  項番 | 説明
  --- | ---
  (1) | `th:fragment`属性を使用し、`pagination`という名前でフラグメント化する。 `Page`オブジェクトをパラメータとして受け取るための引数`page`を定義する。
  (2) | `th:object`属性にフラグメントの引数で受け取った`Page`オブジェクトを指定し、以降の処理でオブジェクト名を省略してプロパティを指定可能にする。
  (3) | `<ul>`要素を出力する。 `th:if`属性を用いてページの要素数が0ではない場合のみ`<ul>`要素を出力するように制御している。 ページネーションリンクであることを示すクラス名`pagination`を指定している。
  (4) | `th:with`属性にてページネーションリンクを表示する際に使用するローカル変数を定義している。 `pageLinkMaxDispNum`には、「指定したページに移動するためのリンク」の最大表示数を指定する。 `disabledHref`には、ページリンク押下時の動作を無効化する場合(`active`状態、または`disabled`状態)に`th:href`属性に指定する値を指定する。 `currentUrl`には、各ページリンクの`th:href`属性の設定に使用するURLのパスを設定する。 Note disabledHrefの設定値について 実装例では、`disabledHref`には`javascript:void(0)`を設定している。ページリンク押下時の動作を無効化するだけであれば、実装例と同じ設定でよい。 ただし、実装例の設定でページリンクにフォーカスを移動又はマウスオーバーした場合、ブラウザのステータスバーに`javascript:void(0)`が表示されることがある。この挙動を変えたい場合は、JavaScriptを使用してページリンク押下時の動作を無効化する必要がある。 Note currentUrlの設定値について 実装例では`th:href`属性に前回リクエストのURLを指定するため、`currentUrl`に`HttpServletRequest`オブジェクトから取得したリクエストURIを設定している。`th:href`属性に前回リクエストのURLを指定するのであれば、実装例と同じ設定でよい。 `th:href`属性に前回リクエストのURL以外を設定する場合は、アプリケーションの要件に応じて`th:href`属性の指定方法を変更すること。`th:href`属性に設定する値はフラグメントのパラメータとして受け取ることで対応可能である。
  (5) | 最初のページに移動するためのリンクを出力する。 リンク先が現在のページである場合は`disabled`状態としている。 リンクが不要な場合は`<li>`要素ごと削除すること。
  (6) | ページ移動するためのリクエストを送信する`<a>`要素を出力する。 `th:href`属性は、現在のページが最初のページである場合は、ページリンク押下時の動作を無効化するため`disabledHref`を指定する。そうでない場合には、リンクURL式`@{}`を使用してリクエストURLを生成して指定する。リンクURL式`@{}`に指定するパスには(4)で定義した`currentUrl`を、パラメータにはページ位置と取得件数を指定する。 `th:href`属性に指定するリクエストURLの生成の詳細については、「リクエストURLを生成する」を参照されたい。 リンクとして表示する文字列は直接記載するか`th:text`属性を用いて出力する。 `<a>`要素の基本的な構造は、以降の`<a>`要素も同様であるため説明は省略する。
  (7) | 前のページに移動するためのリンクを出力する。 リンク先が現在のページである場合は`disabled`状態としている。 `<a>`要素の属性は、現在のページが最初のページであるかを判定し、`th:href`属性に値を設定している。 リンクが不要な場合は`<li>`要素ごと削除すること。
  (8) | 指定したページに移動するためのリンクを`th:each`属性を利用し、繰り返し処理を行うことで出力する。 `th:each`属性に指定する配列は、「指定したページに移動するためのリンクの出力範囲の計算」で作成したカスタムダイアレクトを使用して取得する。 リンクが不要な場合は`<li>`要素ごと削除すること。
  (9) | 次のページに移動するためのリンクを出力する。 リンク先が現在のページである場合は`disabled`状態としている。 リンクが不要な場合は`<li>`要素ごと削除すること。
  (10) | 最後のページに移動するためのリンクを出力する。 リンク先が現在のページである場合は`disabled`状態としている。 リンクが不要な場合は`<li>`要素ごと削除すること。

###### 4.5.2.4.1.2.3. フラグメントを利用してページネーションリンクを表示する

「テンプレートHTML（フラグメント）にページネーションリンクのHTMLを実装する」で実装したフラグメントを利用してページネーションリンクを表示するテンプレートHTML実装例を以下に示す。

- テンプレートHTML(ページネーションを行う画面)

      <!--/* ... */-->

      <!--/* (1) */-->
      <div th:replace="~{pgnt/fragment :: pagination (${page})}"></div>

      <!--/* ... */-->

  項番 | 説明
  --- | ---
  (1) | `th:replace`属性を使用して、テンプレートである`pgnt/fragment.html`の`pagination`フラグメントの内容で`div`タグ以下の内容を置換している。 パラメータとして`Page`オブジェクトを指定している。

- 出力されるHTML

  下記の出力例は、`?page=0&size=10`を指定して検索した際の結果である。

      <ul>
          <li class="disabled"><a href="javascript:void(0)">&lt;&lt;</a></li>
          <li class="disabled"><a href="javascript:void(0)">&lt;</a></li>
          <li class="active"><a href="javascript:void(0)">1</a></li>
          <li><a href="?page=1&size=10">2</a></li>
          <li><a href="?page=2&size=10">3</a></li>
          <li><a href="?page=3&size=10">4</a></li>
          <li><a href="?page=4&size=10">5</a></li>
          <li><a href="?page=5&size=10">6</a></li>
          <li><a href="?page=6&size=10">7</a></li>
          <li><a href="?page=7&size=10">8</a></li>
          <li><a href="?page=8&size=10">9</a></li>
          <li><a href="?page=9&size=10">10</a></li>
          <li><a href="?page=1&size=10">&gt;</a></li>
          <li><a href="?page=9&size=10">&gt;&gt;</a></li>
      </ul>

ページネーションリンク用のスタイルシートを用意しないと以下のような表示となる。
見てわかる通り、ページネーションリンクとして成立していない。
ページネーションリンクとして成立する最低限のスタイルシートの定義の追加を行うと、以下のような表示となる。

- 画面イメージ

ページネーションリンクとして成立したが、以下2つの問題が残る。

- 押下できるリンクと押下できないリンクの区別ができない。

- 現在表示しているページ位置がわからない。

上記の問題を解決する手段として、Bootstrap v3.2.0のスタイルシートを適用すると、以下のような表示となる。

- 画面イメージ

- スタイルシート

  bootstrapのcssファイルは、webjarsで提供されているものを使用する。
  以下、ページネーション関連のスタイル定義の抜粋。

      .pagination {
        display:inline-block;
        padding-left:0;
        margin:20px 0;
        border-radius:4px
      }
      .pagination>li {
        display:inline
      }
      .pagination>li > a,.pagination>li > span {
        position:relative;
        float:left;
        padding:6px 12px;
        margin-left:-1px;
        line-height:1.42857143;
        color:#428bca;
        text-decoration:none;
        background-color:#fff;
        border:1px solid #ddd
      }
      .pagination > li:first-child > a,
      .pagination > li:first-child > span {
        margin-left:0;
        border-top-left-radius:4px;
        border-bottom-left-radius:4px
      }
      .pagination > li:last-child > a,
      .pagination > li:last-child > span {
        border-top-right-radius:4px;
        border-bottom-right-radius:4px
      }
      .pagination > li > a:hover,
      .pagination > li > span:hover,
      .pagination > li > a:focus,
      .pagination > li > span:focus {
        color:#2a6496;
        background-color:#eee;
        border-color:#ddd
      }
      .pagination > .active > a,
      .pagination > .active > span,
      .pagination > .active > a:hover,
      .pagination > .active > span:hover,
      .pagination > .active > a:focus,
      .pagination > .active > span:focus {
        z-index:2;
        color:#fff;
        cursor:default;
        background-color:#428bca;
        border-color:#428bca
      }
      .pagination > .disabled > span,
      .pagination > .disabled > span:hover,
      .pagination > .disabled > span:focus,
      .pagination > .disabled > a,
      .pagination > .disabled > a:hover,
      .pagination > .disabled > a:focus {
        color:#777;
        cursor:not-allowed;
        background-color:#fff;
        border-color:#ddd
      }
      .pagination-lg > li > a,
      .pagination-lg > li > span {
        padding:10px 16px;
        font-size:18px
      }
      .pagination-lg > li:first-child > a,
      .pagination-lg > li:first-child > span {
        border-top-left-radius:6px;
        border-bottom-left-radius:6px
      }
      .pagination-lg > li:last-child > a,
      .pagination-lg > li:last-child > span {
        border-top-right-radius:6px;
        border-bottom-right-radius:6px
      }
      .pagination-sm > li > a,
      .pagination-sm > li > span {
        padding:5px 10px;
        font-size:12px
      }
      .pagination-sm > li:first-child > a,
      .pagination-sm > li:first-child > span {
        border-top-left-radius:3px;
        border-bottom-left-radius:3px
      }
      .pagination-sm > li:last-child > a,
      .pagination-sm > li:last-child > span {
        border-top-right-radius:3px;
        border-bottom-right-radius:3px
      }

- テンプレートHTML

  テンプレートHTMLでは配置したcssファイルを読み込む定義を追加する。

      <!--/* ... */-->

      <link rel="stylesheet" th:href="@{/webjars/bootstrap/dist/css/bootstrap.min.css}">

      <!--/* ... */-->

##### 4.5.2.4.1.3. ページネーション情報の表示

ページネーションに関連する情報(合計件数、合計ページ数、表示ページ数など)を表示するための実装例を以下に示す。

ここでは、どの画面でも共通のページネーション情報を表示するため、テンプレートHTML（フラグメント）に実装する。また、ページネーション情報に出力する「該当ページの表示データ範囲」については、計算ロジックを伴うため式オブジェクトに実装する。

- 画面例

- テンプレートHTML（フラグメント）

      <!--/* ... */-->

      <div th:fragment="paginationInfo (page)" th:object="${page}" th:remove="tag">

        <!--/* (1) */-->
        <div class="text-center"
          th:text="|*{totalElements} results|"></div>

        <!--/* (2), (3) */-->
        <div th:if="*{totalElements} != 0" class="text-center"
          th:text="|*{number + 1} / *{totalPages} Pages|"></div>

      </div>

      <!--/* ... */-->

  項番 | 説明
  --- | ---
  (1) | 検索条件に一致するデータの合計件数を表示する場合は、`Page`オブジェクトの`totalElements`プロパティから値を取得する。
  (2) | 表示しているページのページ数を表示する場合は、`Page`オブジェクトの`number`プロパティから値を取得し、`+1`する。 `Page`オブジェクトの`number`プロパティは`0`開始のため、ページ番号を表示する際は`+1`が必要となる。
  (3) | 検索条件に一致するデータの合計ページ数を表示する場合は、`Page`オブジェクトの`totalPages`プロパティから値を取得する。

該当ページの表示データ範囲を表示するための実装例を以下に示す。
該当ページの表示データ範囲の表示では最大で3つの変数を用いた複雑な計算ロジックを実装することになるため式オブジェクトを用いた実装例を示す。
ここでは、式オブジェクトへ追加するメソッドの実装例のみを示す。式オブジェクトの登録や作成したカスタムダイアレクトの設定等については、「リンクの出力範囲を計算する式オブジェクトを実装する」を参照されたい。

- 画面例

「リンクの出力範囲を計算する式オブジェクトを実装する」で作成した式オブジェクトに新たにメソッドを実装する。実装例を以下に示す。

- 式オブジェクト

      public class PageInfo {

          // omitted

          // (1)
          public int firstItemNumInPage(Page<T> page) {
              return page.getNumber() * page.getSize() + 1;
          }

          // (2)
          public int lastItemNumInPage(Page<T> page) {
              return page.getNumber() * page.getSize() + page.getNumberOfElements();
          }

      }

  項番 | 説明
  --- | ---
  (1) | `Page`オブジェクトを引数に取り、該当ページの表示データの開始位置を返すメソッドを定義する。 `Page`オブジェクトの`number`プロパティは`0`開始のため、データ開始位置を表示する際は`+1`が必要となる。
  (2) | `Page`オブジェクトを引数に取り、該当ページの表示データの終了位置を返すメソッドを定義する。 最終ページは端数となる可能性があるので、`numberOfElements`を加算する必要がある。

- テンプレートHTML（フラグメント）

      <!--/* ... */-->

      <div th:fragment="paginationInfo (page)" th:object="${page}" th:remove="tag">

        <!--/* (4), (5) */-->
        <div class="text-center"
          th:text="|${#pageInfo.firstItemNumInPage(page)} - ${#pageInfo.lastItemNumInPage(page)}|">
        </div>

      </div>

      <!--/* ... */-->

  項番 | 説明
  --- | ---
  (4) | 開始位置を式オブジェクトを使用して取得する。
  (5) | 終了位置を式オブジェクトを使用して取得する。

  Tip

  数値のフォーマットについて

  表示する数値をフォーマットする必要がある場合は、Thymeleafから提供されているユーティリティメソッド`#numbers.formatInteger()`を使用する。

上記で定義したフラグメントを使用してページネーション情報を表示するテンプレートHTML（ページネーションを行う画面）の実装例は以下の通りとなる。

- テンプレートHTML（ページネーションを行う画面）

      <!--/* ... */-->

      <div th:replace="~{pgnt/fragment :: paginationInfo (${page})}"></div>

      <!--/* ... */-->

## 4.5.3. Appendix

### 4.5.3.1. `PageableHandlerMethodArgumentResolver`のプロパティ値について

`PageableHandlerMethodArgumentResolver`で指定できるプロパティは以下の通り。
アプリケーションの要件に応じて、値を変更すること。

> 項番 | プロパティ名 | 説明 | デフォルト値
> --- | --- | --- | ---
> | maxPageSize | 取得件数として許可する最大値を指定する。 指定された取得件数が`maxPageSize`を超えていた場合は、`maxPageSize`が取得件数となる。 | `2000`
> | fallbackPageable | アプリケーション全体のページ位置、取得件数、ソート条件のデフォルト値を指定する。 ページ位置、取得件数、ソート条件が指定されていない場合は、fallbackPageableに設定されている値が適用される。 | ページ位置 : `0` 取得件数 : `20` ソート条件 : `null`
> | oneIndexedParameters | ページ位置の開始値を指定する。 `false`を指定した場合はページ位置の開始値は`0`となり、`true`を指定した場合はページ位置の開始値は`1`となる。 | `false`
> | pageParameterName | ページ位置を指定するためのリクエストパラメータ名を指定する。 | `page`
> | sizeParameterName | 取得件数を指定するためのリクエストパラメータ名を指定する。 | `size`
> | prefix | ページ位置と取得件数を指定するためのリクエストパラメータの接頭辞(ネームスペース)を指定する。 デフォルトのパラメータ名がアプリケーションで使用するパラメータと衝突する場合は、ネームスペースを指定することでリクエストパラメータ名の衝突を防ぐことが出来る。 prefixを指定すると、ページ位置を指定するためのリクエストパラメータ名は`prefix + pageParameterName`、取得件数を指定するためのリクエストパラメータ名は`prefix + sizeParameterName`となる。 | `""` (ネームスペースなし)
> | qualifierDelimiter | 同一リクエストで複数のページ検索が必要になる場合、ページ検索に必要な情報(検索対象のページ位置、取得件数など)を区別するために、リクエストパラメータ名は`qualifier + delimiter + 標準パラメータ名`の形式で指定する。 本プロパティは、上記形式の中の`delimiter`の値を設定する。 この設定を変更する場合は、`SortHandlerMethodArgumentResolver`の`qualifierDelimiter`設定も合わせて変更する必要がある。 | “`_`“

> Note

> maxPageSizeの設定値について

> デフォルト値は`2000`であるが、アプリケーションが許容する最大値に設定を変更することを推奨する。

> アプリケーションが許可する最大値が`100`ならば、`maxPageSize`も`100`に設定する。

> Note

> fallbackPageableの設定方法について

> アプリケーション全体に適用するデフォルト値を変更する場合は、`fallbackPageable`プロパティにデフォルト値が定義されている`Pageable`(`org.springframework.data.domain.PageRequest`) オブジェクトを設定する。

> ソート条件のデフォルト値を変更する場合は、`SortHandlerMethodArgumentResolver`の`fallbackSort`プロパティにデフォルト値が定義されている`org.springframework.data.domain.Sort`オブジェクトを設定する。

開発するアプリケーション毎に変更が想定される以下の項目について、デフォルト値を変更する際の設定例を以下に示す。

- 取得件数として許可する最大値(`maxPageSize`)

- アプリケーション全体のページ位置、取得件数のデフォルト値(`fallbackPageable`)

- ソート条件のデフォルト値(`fallbackSort`)

    @EnableAspectJAutoProxy
    @EnableWebMvc
    @Configuration
    public class SpringMvcConfig implements WebMvcConfigurer {

        @Override
        public void addArgumentResolvers(
                List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(pageableHandlerMethodArgumentResolver());
        }

        @Bean
        public PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver() {
            PageableHandlerMethodArgumentResolver bean = new PageableHandlerMethodArgumentResolver(sortHandlerMethodArgumentResolver()); // (5)
            bean.setMaxPageSize(100); // (1)
            bean.setFallbackPageable(pageRequest()); // (2)
            return bean;
        }

        @Bean
        public PageRequest pageRequest() {
            return PageRequest.of(0, 50); // (3)(4)
        }

        @Bean
        public SortHandlerMethodArgumentResolver sortHandlerMethodArgumentResolver() {
            SortHandlerMethodArgumentResolver bean = new SortHandlerMethodArgumentResolver();
            bean.setFallbackSort(sort()); // (6)
            return bean;
        }

        @Bean
        public Sort sort() {
            List<Order> list = new ArrayList<Order>();
            list.add(order()); // (8)
            return Sort.by(list); // (7)
        }

        @Bean
        public Order order() {
            return new Order(Direction.DESC, "article_id"); // (9)(10)
        }

項番 | 説明
--- | ---
(1) | 上記例では取得件数の最大値を`100`に設定している。取得件数(size)に`101`以上が指定された場合は、`100`に切り捨てて検索が行われる。
(2) | `org.springframework.data.domain.PageRequest`のインスタンスを生成し、`fallbackPageable`に設定する。 spring-data-commons 2.2.0より`PageRequest`クラスからpublicなコンストラクタが削除されたため、factory-methodを利用してstaticな`PageRequest#of`メソッドによりBeanを生成する必要がある。
(3) | `PageRequest#of`メソッドの第1引数に、ページ位置のデフォルト値を指定する。 上記例では`0`を指定しているため、デフォルト値は変更していない。
(4) | `PageRequest#of`メソッドの第2引数に、取得件数のデフォルト値を指定する。 上記例ではリクエストパラメータに取得件数の指定がない場合の取得件数は`50`となる。
(5) | `PageableHandlerMethodArgumentResolver`のコンストラクタとして、`SortHandlerMethodArgumentResolver`のインスタンスを設定する。
(6) | `Sort`のインスタンスを生成し、`fallbackSort`に設定する。 spring-data-commons 2.2.0より`Sort`クラスからpublicなコンストラクタが削除されたため、factory-methodを利用してstaticな`Sort#by`メソッドによりBeanを生成する必要がある。
(7) | `Sort#by`メソッドの第1引数に、デフォルト値として使用する`Order`オブジェクトのリストを設定する。
(8) | `Order`のインスタンスを生成し、デフォルト値として使用する`Order`オブジェクトのリストに追加する。 Note 指定したソート条件のクエリへの反映法はO/RMapperにより異なる。 詳細については以下を参照されたい。 ドメイン層の実装(MyBatis3編)
(9) | `Order`のコンストラクタの第1引数に、ソート順(ASC/DESC)を指定する。
(10) | `Order`のコンストラクタの第2引数に、ソート項目を指定する。

    <mvc:annotation-driven>
        <mvc:argument-resolvers>
            <bean
                class="org.springframework.data.web.PageableHandlerMethodArgumentResolver">
                <!-- (1) -->
                <property name="maxPageSize" value="100" />
                <!-- (2) -->
                <property name="fallbackPageable">
                    <bean class="org.springframework.data.domain.PageRequest" factory-method="of">
                        <!-- (3) -->
                        <constructor-arg index="0" value="0" />
                        <!-- (4) -->
                        <constructor-arg index="1" value="50" />
                    </bean>
                </property>
                <!-- (5) -->
                <constructor-arg index="0">
                    <bean class="org.springframework.data.web.SortHandlerMethodArgumentResolver">
                        <!-- (6) -->
                        <property name="fallbackSort">
                            <bean class="org.springframework.data.domain.Sort" factory-method="by">
                                <!-- (7) -->
                                <constructor-arg index="0">
                                    <list>
                                        <!-- (8) -->
                                        <bean class="org.springframework.data.domain.Sort.Order">
                                            <!-- (9) -->
                                            <constructor-arg index="0" value="DESC" />
                                            <!-- (10) -->
                                            <constructor-arg index="1" value="lastModifiedDate" />
                                        </bean>
                                        <!-- (8) -->
                                        <bean class="org.springframework.data.domain.Sort.Order">
                                            <constructor-arg index="0" value="ASC" />
                                            <constructor-arg index="1" value="id" />
                                        </bean>
                                    </list>
                                </constructor-arg>
                            </bean>
                        </property>
                    </bean>
                </constructor-arg>
            </bean>
        </mvc:argument-resolvers>
    </mvc:annotation-driven>

項番 | 説明
--- | ---
(1) | 上記例では取得件数の最大値を`100`に設定している。取得件数(size)に`101`以上が指定された場合は、`100`に切り捨てて検索が行われる。
(2) | `org.springframework.data.domain.PageRequest`のインスタンスを生成し、`fallbackPageable`に設定する。 spring-data-commons 2.2.0より`PageRequest`クラスからpublicなコンストラクタが削除されたため、factory-methodを利用してstaticな`PageRequest#of`メソッドによりBeanを生成する必要がある。
(3) | `PageRequest#of`メソッドの第1引数に、ページ位置のデフォルト値を指定する。 上記例では`0`を指定しているため、デフォルト値は変更していない。
(4) | `PageRequest#of`メソッドの第2引数に、取得件数のデフォルト値を指定する。 上記例ではリクエストパラメータに取得件数の指定がない場合の取得件数は`50`となる。
(5) | `PageableHandlerMethodArgumentResolver`のコンストラクタとして、`SortHandlerMethodArgumentResolver`のインスタンスを設定する。
(6) | `Sort`のインスタンスを生成し、`fallbackSort`に設定する。 spring-data-commons 2.2.0より`Sort`クラスからpublicなコンストラクタが削除されたため、factory-methodを利用してstaticな`Sort#by`メソッドによりBeanを生成する必要がある。
(7) | `Sort#by`メソッドの第1引数に、デフォルト値として使用する`Order`オブジェクトのリストを設定する。
(8) | `Order`のインスタンスを生成し、デフォルト値として使用する`Order`オブジェクトのリストに追加する。 Note 指定したソート条件のクエリへの反映法はO/RMapperにより異なる。 詳細については以下を参照されたい。 ドメイン層の実装(MyBatis3編)
(9) | `Order`のコンストラクタの第1引数に、ソート順(ASC/DESC)を指定する。
(10) | `Order`のコンストラクタの第2引数に、ソート項目を指定する。

### 4.5.3.2. `SortHandlerMethodArgumentResolver`のプロパティ値について

`SortHandlerMethodArgumentResolver`で指定できるプロパティは以下の通り。
アプリケーションの要件に応じて、値を変更すること。

項番 | プロパティ名 | 説明 | デフォルト値
--- | --- | --- | ---
| fallbackSort | アプリケーション全体のソート条件のデフォルト値を指定する。 ソート条件が指定されていない場合は、fallbackSortに設定されている値が適用される。 | `null` (ソート条件なし)
| sortParameter | ソート条件を指定するためのリクエストパラメータ名を指定する。 デフォルトのパラメータ名がアプリケーションで使用するパラメータと衝突する場合は、リクエストパラメータ名を変更することで衝突を防ぐことができる。 | `sort`
| propertyDelimiter | ソート項目及びソート順(ASC,DESC)の区切り文字を指定する。 | “`,`“
| qualifierDelimiter | 同一リクエストで複数のページ検索が必要になる場合、ページ検索に必要な情報(ソート条件)を区別するために、リクエストパラメータ名は`qualifier + delimiter + sortParameter`の形式で指定する。 本プロパティは、上記形式の中の`delimiter`の値を設定する。 | “`_`“