# mybatisについて(4) ## mybatisについて これは今まで[1](https://hackmd.io/@ka-777/Hkdqwxvid),[2](https://hackmd.io/@ka-777/H1GFEtiod),[3](https://hackmd.io/@ka-777/B1Rokojj_)とやってきたのでそちらを参照してください 今回でようやく終わりです(長かった) 今回はページネーションを実装していきます。 ページネーションとは、簡単に言うと、複数のページに分けることで下にスクロールし続けなくてもいいようにする方法 一番分かりやすい例としては、検索する時に一番下まで行くと次のページとかでページを選ぶことができますがそれのことです。 ## 4つ目のアプリ(最後) ### フォルダ構造 ![](https://i.imgur.com/uEBR1AB.png) 今回も前回の続きなのでXMLまでできていない人は過去の分から作るのをおすすめします ### pom.xmlの編集 今回はpageHelperというのを使ってページネーションを実現するのでそれを追加していきます。 1つ目はSpring Data JPAを追加しています。 ``` <dependencies> <!-- dependenciesタグ内に、以下の2つを追加します --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> ``` >PageHelperはMyBatisのページネーションに物理ページングを追加してくれる便利なやつです >これがない場合はDBから全部持ってきてから必要なものをプログラムで識別するためデータ量が多いと大変です >しかしPageHelperだとDBから対象のデータだけをもってくるのでデータ量が多くても便利です。 >またJPAを入れた理由はこれをいれると「Spring Data Commons」というのも一緒に含まれます。この中にページ情報を扱うpageableというのがあるのでこれを使うために入れています ### コントローラの編集 コントローラにPageableの処理を追加していきます TeacherController.java ``` package dev.itboot.mb.controller; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import dev.itboot.mb.model.Teacher; import dev.itboot.mb.service.TeacherService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Controller public class TeacherController { private final TeacherService service; // Pageableを追加 @GetMapping("/") public String getAllTeachers(Model model, @PageableDefault(size = 5) Pageable pageable) { model.addAttribute("page", service.selectAll(pageable)); return "list"; } @GetMapping("/add") public String addTeacher(@ModelAttribute Teacher teacher) { return "form"; } @PostMapping("/process") public String process(@Validated @ModelAttribute Teacher teacher, BindingResult result) { if (result.hasErrors()) { return "form"; } service.save(teacher); return "redirect:/"; } @GetMapping("/edit/{id}") public String editTeacher(@PathVariable Long id, Model model) { model.addAttribute("teacher", service.selectByPrimaryKey(id)); return "form"; } @GetMapping("/delete/{id}") public String deleteTeacher(@PathVariable Long id) { service.deleteByPrimaryKey(id); return "redirect:/"; } } ``` >@PageableDefaultで1ページの表示データ数を指定することができます(今回は1ページの最大数は5件まで) >そしてservice.selectAll(pageable)でページの情報をそのままサービス層に渡します ### サービス層の編集 先ほど説明したコントローラ層からPageableを受け取ったのでそれをサービス層に追加していきます TeacherService.java ``` package dev.itboot.mb.service; import java.util.List; import org.apache.ibatis.session.RowBounds; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import dev.itboot.mb.mapper.TeacherMapper; import dev.itboot.mb.model.Teacher; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Transactional @Service public class TeacherService { private final TeacherMapper mapper; // Pageableを追加 public Page<Teacher> selectAll(Pageable pageable) { RowBounds rowBounds = new RowBounds( (int)pageable.getOffset(), pageable.getPageSize()); List<Teacher> teachers = mapper.selectAll(rowBounds); Long total = mapper.count(); return new PageImpl<>(teachers, pageable, total); } public Teacher selectByPrimaryKey(Long id) { return mapper.selectByPrimaryKey(id); } public void save(Teacher teacher) { if (teacher.getId() == null) { mapper.insert(teacher); } else { mapper.updateByPrimaryKey(teacher); } } public void deleteByPrimaryKey(Long id) { mapper.deleteByPrimaryKey(id); } } ``` >RowBoundsはMyBatis側のページ情報で引数にoffset(ページ位置)とlimit(1ページ辺りの表示件数)を指定します >PageImplはPageインターフェースの実装クラスのこと >引数にcontent(内容),pageable(ページング情報),total(合計)を指定します ### Mapperの編集 MapperにcountとRowBoundsを追加するためにTeacherMapper.javaを編集していきます TeacherMapper.java ``` package dev.itboot.mb.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.session.RowBounds; import dev.itboot.mb.model.Teacher; @Mapper public interface TeacherMapper { // countを追加 Long count(); // RowBoundsを追加 List<Teacher> selectAll(RowBounds rowBounds); Teacher selectByPrimaryKey(Long id); int insert(Teacher record); int updateByPrimaryKey(Teacher record); int deleteByPrimaryKey(Long id); } ``` >今回は合計を取得するためにcount,ページイング情報を渡すためにRowBoundsを追加しています ### XMLにcountを追加 TeacherMapper.xml ``` <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="dev.itboot.mb.mapper.TeacherMapper"> <!-- COUNTを追加 --> <select id="count" resultType="Long"> SELECT COUNT(*) FROM teacher </select> <select id="selectAll" resultType="Teacher"> SELECT * FROM teacher </select> <select id="selectByPrimaryKey" resultType="Teacher"> SELECT * FROM teacher WHERE id = #{id} </select> <insert id="insert"> INSERT INTO teacher(user_name, email) VALUES(#{userName}, #{email}) </insert> <update id="updateByPrimaryKey"> UPDATE teacher SET user_name = #{userName}, email = #{email} WHERE id = #{id} </update> <delete id="deleteByPrimaryKey"> DELETE FROM teacher WHERE id = #{id} </delete> </mapper> ``` > 合計を取得するためにxmlにもcountを追加します ### HTML(Thymeleaf)にページネーションを追加 list.html ``` <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <th:block th:insert="fragments/base :: header"></th:block> </head> <body> <div class="container-fluid"> <h4>MyBatis APP</h4> <!-- page.totalPagesに変更 --> <table th:if="${page.totalPages > 0}" class="table"> <thead> <tr> <th>#</th> <th>名前</th> <th>E-Mail</th> <th></th> </tr> </thead> <tbody> <tr th:each="teacher : ${page}" th:object="${teacher}"> <td th:text="*{id}"></td> <td th:text="*{userName}"></td> <td th:text="*{email}"></td> <td class="py-1"> <a th:href="@{/edit/{id}(id=*{id})}" class="btn btn-outline-primary"> <i class="fa fa-pencil"></i> 編集 </a> <a th:href="@{/delete/{id}(id=*{id})}" class="btn btn-outline-danger"> <i class="fa fa-trash"></i> 削除 </a> </td> </tr> </tbody> </table> <div class="row"> <div class="col-auto mr-auto"> <a th:href="@{/add}" class="btn btn-outline-primary"> <i class="fa fa-plus"></i> 新規追加 </a> </div> <!-- ページネーションを追加 --> <th:block th:insert="fragments/pagination :: pagination"> </th:block> </div> </div> <th:block th:insert="fragments/base :: scripts"></th:block> </body> </html> ``` > page.totalPagesは、合計のページ数を表していて、プログラムでは合計ページが0ページ以上の場合にtableタグを表示しています 次にpagination.htmlを作成していきます 今回はfragmentsを右クリック→新規→その他で名前をpagination.htmlで作成します。 pagination.html ``` <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> </head> <body> <!-- ページネーションを追加 --> <th:block th:fragment="pagination"> <div th:if="${page.totalPages > 0}" class="col-auto"> <nav> <ul class="pagination"> <!-- 前へ --> <li th:if="${page.hasPrevious()}" class="page-item"> <a class="page-link" th:href="@{/(page=${page.number - 1})}"> 前へ </a> </li> <!-- ページ番号 --> <th:block th:each="i: ${#numbers.sequence(0, page.totalPages - 1)}" th:switch="${page.number}"> <li th:case="${i}" class="page-item active"> <a class="page-link" th:text="${i + 1}"> </a> </li> <li th:case="*" class="page-item"> <a class="page-link" th:href="@{/(page=${i})}" th:text="${i + 1}"> </a> </li> </th:block> <!-- 次へ --> <li th:if="${page.hasNext()}" class="page-item"> <a class="page-link" th:href="@{/(page=${page.number + 1})}"> 次へ </a> </li> </ul> </nav> </div> </th:block> </body> </html> ``` ここは新しいことが多いのでわからないことが多いと思います。 >まずpage.hasPrevious()で前のページがあるかどうかを確認し、あればtrueを返します >そしてtrueの時にタグを表示するように記述しています。 >page.numberは現在のページ番号を表しているので、前のページはこれから-1をするだけで表すことができます。 >#numbers.sequence()はThymeleafについてるものでこれを使うと第一引数から第二引数まで連番を作成してくれます。 >ページは0から始まるので最大のページは合計ページ数から-1の値となります >また、現在のページとそれ以外のページで分ける場合にはif-else文を使いたくなるとこですが、Thymeleafにはないのでswitch-caseやif-unlessを使って処理を分けます >page.hasNext()で次のパージがあるかどうか確認してあるならtrueを返します >また次のページは現在のページ+1するだけでOK かなり長くなりましたがこのくらいの説明で終わらせます ### データローダの作成 dev.itboot.mbを右クリック→新規→クラスで作成します 今回はパッケージの最後に.configを追加して、名前をDataLoader.javaにします DataLoader.java ``` package dev.itboot.mb.config; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import dev.itboot.mb.model.Teacher; import dev.itboot.mb.service.TeacherService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Component public class DataLoader implements CommandLineRunner { private final TeacherService service; @Override public void run(String... args) throws Exception { for (int i = 0; i < 5; i++) { Teacher teacher = new Teacher(); teacher.setUserName("テスト"); teacher.setEmail("test@example.com"); service.save(teacher); } } } ``` >CommandLineRunnerをimplementsしたクラスは、コマンドラインで実行するアプリを作成することができます。処理内容はrunメソッド内に記述します >@Componentはこれがついているクラスがコンポーネントクラスというのを示します。これはコンポーネントスキャンの対象なのでSpringBoot起動時にこのクラスが読み込まれます。 ### 実行 今回も相変わらず付け足すことが多いですがこれで終了です。いつも通り実行して[ここ](http://localhost:8080/)にアクセスします。 すると、以下のような画面がでます ![](https://i.imgur.com/CeCPljO.png) ここから次へか2のボタンを押すと、以下のように遷移します ![](https://i.imgur.com/ZBw37Li.png) ### まとめ これでMybatisのアプリ作成は終わりです かなり長かったですが見た目はこんな簡単な機能だけですので普段使っているサイトのすごさがよくわかりました。 あと難しい・。・ 前回の内容↓↓↓↓↓↓ https://hackmd.io/@ka-777/B1Rokojj_