はじめに
現在のプロジェクトでは、プログラムはJava(Spring-Boot)、フロントはthymeleaf、Bootstrapを使って開発をしています。
開発する中で、時間を要したのがページネーション処理です。同環境での実装例も見当たらず、数日かかっての実装となりました。
そこで、Spring-Boot、Tymeleaf環境でのページネーションの実装方法をご紹介します。同環境でページネーション処理につまずいている方に読んでいただきたいです。
環境
- Spring-Boot 2.1.6.RELEASE https://mvnrepository.com/artifact/org.springframework.boot/spring-boot/2.1.6.RELEASE
- Thymeleaf 3.0.4
https://www.thymeleaf.org/download.html - Bootstrap4 5.1.2
https://tempusdominus.github.io/bootstrap-4/ - ローカル環境:windows 10
実装ページング機能概要
- テーブルでData内容を表示する(3件づつ)
- ページ数の表示、そのページへの遷移が可能
- 現在のページの所在が分かる。
- 「次へ」「前へ」を表出させて遷移できる。
- 「最初」「最後」を表出させて遷移できる。
- 表示するページ数を制限する(3ページ)
ディレクトリ構成
src/
├ java/
│ ├ sampleController.java
│ ├ sampleForm.java
│ ├ sampleDao.java
│ └ member.java
└ resourse/
└ html/
└ sample.html
1-1 Controller
package jp.sbworks.standard.web.controller;
import java.util.HashMap;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import jp.sbworks.standard.web.dao.SampleDao;
import jp.sbworks.standard.web.db.single.pojo.SSample;
@Controller
public class SampleController {
/** 1ページの表示数 */
private final String limit = "3";
/** ページネーションで表示するページ数 */
private int showPageSize = 3;
@Autowired
private SampleDao sampleDao;
@RequestMapping(value = "/sample",method = RequestMethod.GET)
@Transactional(readOnly = true)
public String index(Model model, @RequestParam HashMap<String, String> params) throws Exception {
// パラメータを設定し、現在のページを取得する
String currentPage = params.get("page");
// 初期表示ではパラメータを取得できないので、1ページに設定
if (currentPage == null){
currentPage = "1";
}
// データ取得時の取得件数、取得情報の指定
HashMap<String, String> search = new HashMap<String, String>();
search.put("limit", limit);
search.put("page", currentPage);
int total = 0;
List<SSample> list = null;
try {
// データ総数を取得
total = sampleDao.getMemberListCount();
// データ一覧を取得
list = sampleDao.getMemberList(search);
} catch (Exception e) {
return "error/fatal";
}
// pagination処理
// "総数/1ページの表示数"から総ページ数を割り出す
int totalPage = (total + Integer.valueOf(limit) -1) / Integer.valueOf(limit);
int page = Integer.valueOf(currentPage);
// 表示する最初のページ番号を算出(今回は3ページ表示する設定)
// (例)1,2,3ページのstartPageは1。4,5,6ページのstartPageは4
int startPage = page - (page-1)%showPageSize;
// 表示する最後のページ番号を算出
int endPage = (startPage+showPageSize-1 > totalPage) ? totalPage : startPage+showPageSize-1;
model.addAttribute("list", list);
model.addAttribute("total", total);
model.addAttribute("page", page);
model.addAttribute("totalPage", totalPage);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
return "html/sample/index";
}
}
1-2 DAO
package jp.sbworks.standard.web.dao;
import java.io.IOException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import org.sql2o.Connection;
import org.sql2o.Query;
import org.sql2o.Sql2o;
import jp.sbworks.standard.web.db.single.pojo.SSample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class SampleDao {
@Autowired
// データベースアクセス用のライブラリ
private Sql2o sql2o;
// リスト情報を取得する
public List<SSample> getMemberList(HashMap<String, String> search) throws SQLException, IOException {
// Jdbcに接続する
Connection con = sql2o.open();
con.getJdbcConnection().setAutoCommit(false);
// クエリの作成
Query query = con.createQuery("select ID as id, NAME as name, DEPARTMENT as department, DAY_OF_JOINING as dayOfJoining from SAMPLE limit :limit offset :offset;");
int limit = Integer.valueOf(search.get("limit"));
int page = Integer.valueOf(search.get("page")) - 1;
// 何件情報を取得するかの指定。
query = query.addParameter("limit", limit);
// 何件目からの情報を取得するかの指定(※コントローラからパラメータを使って現在のページ数が分かる。それによって何件目からの情報を取得すればいいのかが分かる。)
query = query.addParameter("offset", limit * page);
return query.executeAndFetch(SSample.class);
}
// リスト情報件数を取得する
public int getMemberListCount() throws SQLException, IOException {
Connection con = sql2o.open();
con.getJdbcConnection().setAutoCommit(false);
Query query = con.createQuery("select count(1) from SAMPLE");
return query.executeAndFetchFirst(Integer.class);
}
}
1-3 DB
package jp.sbworks.standard.web.db.single.pojo;
import java.io.Serializable;
import java.time.LocalDate;
public class SSample implements Serializable {
// ID
public Integer id;
// 名前
public String name;
// 部署
public Integer department;
// 入社日
public LocalDate dayOfJoining;
// 登録者
public String regName;
}
1-4 html
<!DOCTYPE html>
<html lang="ja">
<head>
<script type="text/javascript" th:src="@{/webpack-bundled/Sample/bundled.js}"></script>
</head>
<div class="table-scroll mb-2">
<table class="table table-bordered table-sm mt-1">
<thead>
<tr>
<th>社員ID</th>
<th>名前</th>
<th>部署</th>
<th>入社日</th>
<th>登録者</th>
</tr>
</thead>
<tbody>
<th:block th:each="item, status : ${list}">
<tr>
<td th:text="${item.id}"></td>
<td th:text="${item.name}"></td>
<td th:text="${item.department}"></td>
<td th:text="${item.dayofjoining}"></td>
<td th:text="${item.regname}"></td>
</tr>
</th:block>
</tbody>
</table>
</div>
<!-- ここからページング処理 -->
<nav>
<ul class="pagination pg-blue justify-content-center">
<li th:if="${startPage} > 1" class="page-item ">
<a class="page-link" th:href="@{'/sample?page=1'}" tabindex="-2">最初</a>
</li>
<li th:if="${page} > 1" class="page-item ">
<a class="page-link" th:href="@{'/sample?page=' + ${page-1}}" tabindex="-1">前へ</a>
</li>
<th:block th:if="${endPage}<=0">
<li class="page-item " th:classappend="${i == page} ? active" th:each="i : ${startPage}">
<a class="page-link" th:href="@{'/sample?page=' + ${i}}" th:text="${i}"></a>
</li>
</th:block>
<!-- StartPageからEndPageまでのページ数を表示する -->
<th:block th:if="${endPage}>0">
<li class="page-item " th:classappend="${i == page} ? active" th:each="i : ${#numbers.sequence(startPage, endPage)}">
<a class="page-link" th:href="@{'/sample?page=' + ${i}}" th:text="${i}"></a>
</li>
</th:block>
<li th:if="${page} < ${totalPage}" class="page-item ">
<a class="page-link" th:href="@{'/sample?page=' + ${page+1}}">次へ</a>
</li>
<li th:if="${endPage} < ${totalPage}" class="page-item ">
<a class="page-link" th:href="@{'/sample?page=' + ${totalPage}}">最後</a>
</li>
</ul>
</nav>
現在いるページの所在を分かりやすくするために、Bootstrapを使って色付けをしています。下記は現在表示しているページとして青色で表示する場合の処理を簡単に書きました。
<nav>
<ul class="pagination pg-blue">
…
<li th:classappend="${i == page} ? active" >
結果
下記のように実装できました。
まとめ
ページングの処理は煩雑なりがちですが、基本的な動きはJavaで実装し、Thymeleafで条件分岐、繰り返し処理を活用することで、より可読性が高く、シンプルなプログラムとなります。
私がつまづいた箇所は、2ページ以降のデータの取得です。コントローラでパラメータを使って現在のpage番号を取得することで、取得を始めるデータを特定でき、ページごとに正しいデータを取得することに成功しました。
1ページに表示するデータ数や、表示ページ数の指定をプロジェクト用に沿って替えることで、活用いただければ幸いです。
author/A.Nishihara