EatSmartシステム部ブログ

ウェブサイトの開発や運営に関する情報です。

Spring Boot + Thymeleaf でページネーションを実装する

業務のなかで一覧表示を実装することはよくあると思います 件数が多い場合はページあたりの件数を設け、複数ページに分割します Spring Boot を利用して開発を行っているなかで、この複数ページの分割を実現する仕組みを見つけたので利用してみました

Pageable/Page を利用する

その仕組とは、Pageable/Page というクラスによって提供されています

まずページ分割の設定を行います

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
        resolver.setOneIndexedParameters(true);
        resolver.setMaxPageSize(50);
        argumentResolvers.add(resolver);
    }

}

WebMvcConfigurer を実装したクラスを作成し、 addArgumentResolvers の中でページ分割の設定をしています ページ番号を1からはじめたいので、 setOneIndexedParameters へ true を設定します 1ページあたりの件数を指定するため、 setMaxPageSize へ 50 を設定します

つぎにコントローラを実装します

@GetMapping(value = "/list")
public String list(Pageable pageable, Model model) {
    Page<Bean> page= service.getPage(pageable);
    model.addAttribute("page", page);
    model.addAttribute("path", "/list");
    model.addAttribute("list", page.getContent());
    return "list";
}

コントローラでは、引数に Pageable を設定し、リストを取得するサービスクラスにそのまま渡しています 返却された Page クラスを Thymeleaf で参照できるよう、 model#addAttribute で"page"という名前でセットします また、ページ分割した各ページへのリンクで利用するため"path"という名前で遷移先のパスをセットします このほか、一覧表示する内容は page#getContent で取得することができるので、"list"という名前でセットしました

つぎにサービスクラスを実装します

@Transactional(readOnly = true)
public Page<Bean> getPage(Pageable pageable) {
    List<Bean> list = dao.getList(pageable.getOffset(), pageable.getPageSize());
    int count = dao.getCount();
    return new PageImpl<Bean>(list, pageable, count);
}

先程コントローラから渡された Pageable をもとに、データベースから該当範囲のリストと総数を取得します 表示するリストの範囲は pageable#getOffset / pageable#getPageSize から取得することができるので、DAOの引数に渡しています また、ページ分割するためにはリストの全件を知る必要があるため、取得しています 最後に PageImpl として返却します

最後にテンプレートを実装します ※コントローラで"list"という名前でセットしたリストをイテレータを利用して実装しますが、ここでは省略します

<ul>
    <!-- 前のページの戻る -->
    <li th:class="${page.first} ? 'disabled' : ''" class="disabled">
        <a th:href="@{${path}(page=${page.number})}" class="page-link" href="#" tabindex="-1"><i class="fas fa-chevron-left"></i></a>
    </li>
    <!-- 総数とページあたりの件数をもとにページ分割 -->
    <th:block th:each="i : ${#numbers.sequence(0, page.totalPages - 1)}">
        <li th:class="(${i} == ${page.number}) ? 'active' : ''" class="active"><a th:text="${i + 1}" th:href="@{${path}(page=${i + 1})}" class="page-link" href="#">3</a></li>
    </th:block>
    <!-- 次のページへ進む -->
    <li th:class="${page.last} ? 'disabled' : ''" class="disabled">
        <a th:href="@{${path}(page=${page.number + 2})}" class="page-link" href="#" tabindex="-1"><i class="fas fa-chevron-right"></i></a>
    </li>
</ul>

ページネーションの各リンクは、"path"という名前でセットしたパスに"page"という名前で遷移先のページ番号を指定します ページ番号を1から始めるようにしているので、ページ番号やURLパラメータの調整をしています

まとめ

当初は独自に実装していましたが、Spring Boot に用意されている仕組みを利用して実装し直してみました 仕組みを理解するため車輪の再発明をすることも重要ですが、今後のことを考えより一般的な方法を選ぶことも必要だと感じます