LoginSignup
4
3

More than 1 year has passed since last update.

【Train with Spring】5. ページネーション

Posted at

初めに

この記事は主に同期に対してSpringを紹介するために作成されました。
なるべく楽をしながらSpringと仲良くなるのが目的ですので、網羅的な説明は極力避け、目的を達成する手段が幾つかある中から、その内の1つを学ぶ形式で進みます。
また、記事中には正確性に欠ける表現が出現しますが、これも上記と同じ理由です。

前回までのあらすじ

前回は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オブジェクトにはページ番号、ページサイズの他に、全データ数等の追加情報も含まれているため、それらを駆使すれば「最後のページへ移動」のようなページ移動も可能になります。

まとめ

今回はページネーションについて解説しました。

次回は未定です。書くべき事が新たに生まれたら解説します。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3