4.5. ページネーション


4.5.1. Overview

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

検索条件に一致するデータが大量になる場合は、ページネーション機能を使用することを推奨する。
一度に大量のデータを取得し画面に表示すると、以下3点の問題が発生する可能性がある。
  • サーバ側のメモリ枯渇の発生。
    単発のリクエストで問題が発生しなくても、同時に複数実行された場合にjava.lang.OutOfMemoryErrorが発生する可能性がある。
  • ネットワーク負荷の発生。
    不要なデータがネットワークに流れることで、ネットワーク全体にかかる負荷が高くなり、システム全体のレスポンスタイムに影響を与える可能性がある。
  • 画面のレスポンス遅延の発生。
    大量のデータを扱う場合、サーバの処理、ネットワークのトラフィック処理、クライアントの描画処理の全てで時間がかかるため、画面のレスポンスが遅くなる可能性がある。

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

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

項番

説明

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

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

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


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

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

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

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

  • 検索対象のページ位置

  • 取得件数

  • ソート条件

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

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

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

項番

説明

(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)
ページネーションリンクを押下すると、該当ページを表示するためリクエストが送信される。

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)
addArgumentResolversargumentResolversorg.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(ソート条件なし)

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

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

    • PageableHandlerMethodArgumentResolverfallbackPageableプロパティにデフォルト値を定義した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に定義したPageableHandlerMethodArgumentResolverfallbackPageableプロパティにデフォルト値を定義したPageableオブジェクトを指定する。

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


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

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

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

を参照されたい。


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で出力される画面例

    Screen image of content table

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

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

  • 画面例

    Screen image of pagination information(total results, current pages, total pages)
  • 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プロパティから値を取得する。

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

  • 画面例

    Screen image of pagination information(begin position, end position)
  • 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. ページリンクで検索条件を引き継ぐ

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

Processing image of take over search criteria
  • 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. 先頭ページと最終ページに移動するリンクの削除

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

  • 画面例

    Remove page link that move to first & last page
  • 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. 前ページと次ページに移動するリンクの削除

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

  • 画面例

    Remove page link that move to previous & next page
  • 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時のスタイルシートに、以下の定義を追加する。
  • 画面例

    Remove page link that move to previous & next page
  • スタイルシート

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

    項番

    説明

    (1)
    disabledクラスの属性値として、display: none;を指定する。

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

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

  • 画面例

    change max display count of page link that move to specified page
  • JSP

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

    項番

    説明

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

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

    Remove page link that move to specified page
  • 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. ソート条件の指定

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

  • 画面例

    specify the sort condition
  • 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}/resources/vendor/js/jquery.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で出力される画面例

    Screen image of content table

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

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

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

  • 画面例

    Screen image of pagination information(total results, current pages, total pages)
  • テンプレート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つの変数を用いた複雑な計算ロジックを実装することになるため式オブジェクトを用いた実装例を示す。

ここでは、式オブジェクトへ追加するメソッドの実装例のみを示す。式オブジェクトの登録や作成したカスタムダイアレクトの設定等については、「リンクの出力範囲を計算する式オブジェクトを実装する」を参照されたい。
  • 画面例

    Screen image of pagination information(begin position, end position)

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

  • 式オブジェクト

    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の値を設定する。
この設定を変更する場合は、SortHandlerMethodArgumentResolverqualifierDelimiter設定も合わせて変更する必要がある。
_

Note

maxPageSizeの設定値について

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

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

Note

fallbackPageableの設定方法について

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

ソート条件のデフォルト値を変更する場合は、SortHandlerMethodArgumentResolverfallbackSortプロパティにデフォルト値が定義されている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により異なる。

詳細については以下を参照されたい。

(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の値を設定する。
_