4.5. ページネーション¶
4.5.1. Overview¶
本章では、検索条件に一致するデータをページ分割して表示する方法(ページネーション)について説明する。
- サーバ側のメモリ枯渇の発生。単発のリクエストで問題が発生しなくても、同時に複数実行された場合に
java.lang.OutOfMemoryError
が発生する可能性がある。 - ネットワーク負荷の発生。不要なデータがネットワークに流れることで、ネットワーク全体にかかる負荷が高くなり、システム全体のレスポンスタイムに影響を与える可能性がある。
- 画面のレスポンス遅延の発生。大量のデータを扱う場合、サーバの処理、ネットワークのトラフィック処理、クライアントの描画処理の全てで時間がかかるため、画面のレスポンスが遅くなる可能性がある。
4.5.1.1. ページ分割時の一覧画面の表示について¶
4.5.1.2. ページ検索について¶
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
が提供されている。ページネーションリンクを出力する際には、Page
オブジェクトから必要なデータを取得する。3 データベースアクセスとしてSpring Data JPAを使用する場合は、RepositoryのQueryメソッドの引数にPageable
オブジェクトを指定することで、該当ページの情報がPage
オブジェクトとして返却される。合計件数を取得するSQLの発行、ソート条件の追加、該当ページに一致するデータの抽出などの処理が全て自動で行われる。データベースアクセスとして、MyBatisを使用する場合は、Spring Data JPAが自動で行ってくれる処理を、Java(Service)及びSQLマッピングファイル内で実装する必要がある。
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. ページネーションリンクの表示について¶
<t:pagination>
のデフォルト設定で出力されるHTMLを例に、ページネーションリンクの実装例を説明する。4.5.1.3.2.1. ページネーションリンクの構成¶
ページネーションリンクは、以下の要素から構成される。
ページネーションリンクは、以下の状態をもつ。
4.5.1.3.2.2. ページネーションリンクのHTML構造¶
- HTML
- 画面イメージ
4.5.1.3.3. ページネーション情報の表示について¶
- 合計件数
- 検索対象のページ位置
- 取得件数
- ソート条件
4.5.1.4. ページネーション機能使用時の処理フロー¶
Spring Dataより提供されているページネーション機能を利用した際の処理フローは、以下の通り。
項番 説明 (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) ページネーションリンクを押下すると、該当ページを表示するためリクエストが送信される。
4.5.2. How to use¶
ページネーション機能の具体的な使用方法を以下に示す。
4.5.2.1. アプリケーションの設定¶
4.5.2.1.1. Spring Dataのページネーション機能を有効化するための設定¶
Pageable
オブジェクトとしてControllerの引数に設定するための機能を有効化する。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(テンプレートHTML)から参照できるようになる。Note
リクエストパラメータにページ検索に必要な情報の指定がない場合の動作について
ページ検索に必要な情報(検索対象のページ位置、取得件数、ソート条件)がリクエストパラメータに指定されていない場合は、デフォルト値が適用される。 デフォルト値は、以下の通り。
- 検索対象のページ位置 : 0 (1ページ目)
- 取得件数 : 20
- ソート条件 : null (ソート条件なし)
デフォルト値は、以下の2つの方法で変更することができる。
- ハンドラメソッドの
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
のようなOrder By句をQueryに追加することになる。 空の配列(ソート項目なし)Note
@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
(昇順)上記例では、
ORDER BY publishedDate DESC, articleId ASC
のようなOrder By句をQueryに追加することになる。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編)¶
Pageable
オブジェクトより、必要な情報を抜き出してRepositoryに引き渡す。ドメイン層で実装するページ検索処理の詳細については、
を参照されたい。
4.5.2.3. テンプレートHTMLの実装¶
ページ検索処理で取得した Page
オブジェクトからデータを取得し、ページネーションを行う画面に取得したデータ、ページネーションリンク、ページネーションに関する情報(合計件数、合計ページ数、表示ページ数など)を表示する方法について説明する。
そのため、ここでは以下の構成でテンプレートHTMLの実装を行う例を示す。
成果物の構成要素 説明 テンプレートHTML(ページネーションを行う画面) 取得したデータの一覧の表示を実装する テンプレートHTML(フラグメント) すべてのテンプレートHTML(ページネーションを行う画面)で同様の、ページネーションリンクやページネーション情報の表示の実装を共通化する 式オブジェクト ページネーションリンクの出力範囲や該当ページの表示データ範囲の計算ロジックを実装する
page
という名前を指定して Page
オブジェクトにアクセスすることができる。4.5.2.3.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.3.2. ページネーションリンクの表示¶
- 最初のページに移動するためのリンク
- 前のページに移動するためのリンク
- 指定したページに移動するためのリンク
- 次のページに移動するためのリンク
- 最後のページに移動するためのリンク
- リンクの出力範囲を計算する式オブジェクトを実装する
- テンプレートHTML(フラグメント)にページネーションリンクのHTMLを実装する
- テンプレートHTML(ページネーションを行う画面)にフラグメントを利用してページネーションリンクを表示する
Note
指定したページに移動するためのリンクの出力範囲の計算の実装方法について
この章で説明している式オブジェクトを使用した実装方法はあくまでも一例であり、実装方法を規定するものではない。他に以下のような実装方法があるため、プロジェクトの開発方針に則り実装していただきたい。
Page
オブジェクトを拡張し、表示するリンクの範囲(開始位置、終了位置)を計算、保持する。- ControllerのHelperクラスでリンクの範囲(開始位置、終了位置)を計算する。
4.5.2.3.2.1. リンクの出力範囲を計算する式オブジェクトを実装する¶
テンプレート記述例
#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&size=10">1</a></li>
<li><a href="/sample?page=1&size=10">2</a></li>
<li><a href="/sample?page=2&size=10">3</a></li>
<li><a href="/sample?page=3&size=10">4</a></li>
<li><a href="/sample?page=4&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.springsecurity5.dialect.SpringSecurityDialect" />
<bean class="org.thymeleaf.extras.java8time.dialect.Java8TimeDialect" />
<bean class="com.example.sample.dialect.PageInfoDialect"/> <!-- (1) -->
</set>
</property>
</bean>
項番 | 説明 |
---|---|
(1)
|
テンプレートエンジンに作成したカスタムダイアレクトを追加する。
|
以上でカスタムダイアレクトの実装及び設定は完了となる。
4.5.2.3.2.2. テンプレートHTML(フラグメント)にページネーションリンクのHTMLを実装する¶
ページネーションリンクのテンプレートHTMLのフラグメントの実装例を以下に示す。
th:fragment
属性を利用してページネーションリンクの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})}"><<</a> </li> <!--/* (7) */--> <li th:class="*{isFirst()} ? 'disabled'"> <a th:href="*{isFirst()} ? ${disabledHref} : @{{currentUrl}(currentUrl=${currentUrl},page=*{number - 1},size=*{size})}"><</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})}">></a> </li> <!--/* (10) */--> <li th:class="*{isLast()} ? 'disabled'"> <a th:href="*{isLast()} ? ${disabledHref} : @{{currentUrl}(currentUrl=${currentUrl},page=*{totalPages - 1},size=*{size})}">>></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.3.2.3. テンプレートHTML(ページネーションを行う画面)にフラグメントを利用してページネーションリンクを表示する¶
「テンプレート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)"><<</a></li> <li class="disabled"><a href="javascript:void(0)"><</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">></a></li> <li><a href="?page=9&size=10">>></a></li> </ul>
- 画面イメージ
ページネーションリンクとして成立したが、以下2つの問題が残る。
- 押下できるリンクと押下できないリンクの区別ができない。
- 現在表示しているページ位置がわからない。
上記の問題を解決する手段として、Bootstrap v3.0.0のスタイルシートと適用すると、以下のような表示となる。
- 画面イメージ
- スタイルシート
bootstrap v3.0.0 の cssファイルを$WEB_APP_ROOT/resources/vendor/bootstrap-3.0.0/css/bootstrap.css
に配置する。以下、ページネーション関連のスタイル定義の抜粋。.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.428571429; text-decoration: none; background-color: #ffffff; border: 1px solid #dddddd; } .pagination > li:first-child > a, .pagination > li:first-child > span { margin-left: 0; border-bottom-left-radius: 4px; border-top-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 { background-color: #eeeeee; } .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: #ffffff; cursor: default; background-color: #428bca; border-color: #428bca; } .pagination > .disabled > span, .pagination > .disabled > a, .pagination > .disabled > a:hover, .pagination > .disabled > a:focus { color: #999999; cursor: not-allowed; background-color: #ffffff; border-color: #dddddd; }
- テンプレートHTML
テンプレートHTMLでは配置したcssファイルを読み込む定義を追加する。
<!--/* ... */--> <link rel="stylesheet" th:href="@{/resources/vendor/bootstrap/dist/css/bootstrap.min.css}"> <!--/* ... */-->
4.5.2.3.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
プロパティから値を取得する。
- 画面例
「リンクの出力範囲を計算する式オブジェクトを実装する」で作成した式オブジェクトに新たにメソッドを実装する。実装例を以下に示す。
- 式オブジェクト
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
)
<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
オブジェクトのリストに追加する。上記例ではリクエストパラメータにソート条件の指定がない場合はORDER BY lastModifiedDate DESC, id ASC
のようなOrder By句をQueryに追加することになる。 (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
の値を設定する。 “_
”