初めに
この記事は主に同期に対してSpringを紹介するために作成されました。
なるべく楽をしながらSpringと仲良くなるのが目的ですので、網羅的な説明は極力避け、目的を達成する手段が幾つかある中から、その内の1つを学ぶ形式で進みます。
また、記事中には正確性に欠ける表現が出現しますが、これも上記と同じ理由です。
前回までのあらすじ
- 【Train with Spring】1. プロジェクトのセットアップ
- 【Train with Spring】2. 画面遷移とパラメータの受け取り
- 【Train with Spring】3. データベースアクセス1
- 【Train with Spring】4. データベースアクセス2
前回はSpring Data JPAでのリレーションの実装について学びました。
前回がかなり長めだったので、今回はページネーションについて簡単に解説します。
ページネーション
ページネーションとは、データをページ単位で分割する処理の事です。
QiitaでもまとめブログでもGoogle検索でも何でも良いですが、そのようなサイトではデータの一覧を表示する際に、全てのデータを一度に表示せず、ページ単位で表示するようになっています。
例えば、Googleでページネーションと調べた場合は一千万件超えのページがヒットしますが、検索結果に一度に表示されるのはせいぜい10件程度です。
今回は、このページ分割処理をSpringでどう実装するのかを解説します。
仕組み
ページネーションでは、ページ番号(Springでは0始まり)とページサイズの2つのパラメータを基に取得するデータを選択します。
例えば、時系列順に並んでいる100個(0--99)のデータに対してページサイズが10でページ番号が2という条件でデータを取得すると、取得されるのは20番目から29番目のデータです。
Springにおけるページネーション対応
では、Springでページネーションを実装していきます。
といっても、やる事は簡単です。
リポジトリ
リポジトリでは、データを取得するメソッドの引数にPageable
型の引数を追加して、戻り値の型をList<>
からPage<>
に変更するだけです。
package com.example.trainwithspring.repositories;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import com.example.trainwithspring.entities.User;
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByName(String name);
List<User> findByNameContaining(String name);
// 追加
Page<User> findByNameContaining(String name, Pageable pageable);
}
これで、名前での中間一致検索がページネーションに対応しました。
ソートしながらページング
一般的に、ページングはソートと一緒に用いられます。
例えば、名前での中間一致検索をしながら、ユーザーの登録日時順でページングをしたい場合は以下のようにOrderBy〇〇Desc
を付けます。
Page<User> findByNameContainingOrderByCreatedAtDesc(String name, Pageable pageable);
コントローラー
コントローラーでも、マッピングされたメソッドの引数にPageable
型の引数を追加するだけです。
package com.example.trainwithspring.controllers;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.trainwithspring.entities.User;
import com.example.trainwithspring.repositories.UserRepository;
@Controller
public class UserController {
@Autowired // このフィールドはSpringで勝手に用意してくれという意味のおまじない
UserRepository userRepository;
/**
* ユーザーの名前での中間一致検索。
* @param name 検索する名前
* @param model Thymeleafに渡すデータ
* @param pageable ページ情報
* @return Thymeleafのテンプレート名
*/
@GetMapping("/users")
public String users(
@RequestParam(name = "name", required = true) String name,
Model model,
// Pageable型の引数を追加
Pageable pageable) {
// さっき定義したメソッドでユーザーを取得する
Page<User> page = userRepository.findByNameContaining(name, pageable);
List<User> users = page.getContent();
// Thymeleafで前ページ/次ページへのリンクを作れるようにページをそのまま渡す
model.addAttribute(page);
// 利便性のためにusersからユーザー情報を参照できるようにユーザーリストも渡す
model.addAttribute(users);
return "user-list";
}
}
これで完成です。
Pageable
Pageableは、ページ番号とページサイズを保持するためのクラスです。
コントローラーのメソッドの引数に指定すると、リクエストに存在するpage
パラメータとsize
パラメータから自動的にページ情報を取得して、pageable
に代入します。
例えばユーザー一覧の2ページ目(ページサイズ10)を見るにはhttp://localhost:8080/users?page=2&size=10
にアクセスをします。
もっと素朴な書き方をするとこんな感じです。このように、リクエストパラメータからPageable
インスタンスを作成する部分を、引数に設定すると自動化してくれます。
@GetMapping("/users")
public String users(
@RequestParam(name = "name", required = true) String name,
Model model,
@RequestParam(name = "page", required = false, defaultValue = "0") Integer page,
@RequestParam(name = "page", required = false, defaultValue = "10") Integer size) {
// リクエストパラメータを基にPageableを作成
Pageable pageable = Pageable.ofSize(size).withPage(page);
// さっき定義したメソッドでユーザーを取得する
Page<User> userPage = userRepository.findByNameContaining(name, pageable);
List<User> users = userPage.getContent();
// Thymeleafで前ページ/次ページへのリンクを作れるようにページをそのまま渡す
model.addAttribute("page", userPage);
// 利便性のためにusersからユーザー情報を参照できるようにユーザーリストも渡す
model.addAttribute(users);
return "user-list";
}
Thymeleaf
Thymeleafでは、コントローラーから受け取ったページ情報を基に、前ページや次ページへのリンクを作成します。
例えば、http://localhost:8080/users?page=2&size=10
にアクセスした場合
- 次ページへのリンクは
http://localhost:8080/users?page=3&size=10
- 前ページへのリンクは
http://localhost:8080/users?page=1&size=10
- 一度に20項目表示するリンクは
http://localhost:8080/users?page=2&size=20
のように、それぞれのリンクを動的に生成します。
Page
オブジェクトにはページ番号、ページサイズの他に、全データ数等の追加情報も含まれているため、それらを駆使すれば「最後のページへ移動」のようなページ移動も可能になります。
まとめ
今回はページネーションについて解説しました。
次回は未定です。書くべき事が新たに生まれたら解説します。