# JPAを用いた会員情報アプリ ## JPAとは ここは過去にまとめているのでURLを添付しておきます https://hackmd.io/@ka-777/SJjvtYt3D https://hackmd.io/@ka-777/BJqoeHwjD https://hackmd.io/@ka-777/rJtO8_l3D 簡単なアプリも作って説明してあります↑↑↑ ## 目的 CRUD(Create,Read,Update,Delete)の操作をできるようにすること ## アプリ製作 ### パッケージ作成&ファイル作成 ここは今までに何回も説明したので過去のを参照して作ってください また、今回使用する依存関係は、 ・Spring Boot DevTools ・Lombok ・検証 ・Spring Data JPA ・H2 Database ・Thymeleaf ・Spring Web です フォルダ構成は以下の画像の通りなので真似して作ってください ![](https://i.imgur.com/bDZpMu9.png) ### モデルの作成 まずはコードから載せます Employee.java↓ ``` package com.example.demo.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import lombok.Getter; import lombok.Setter; @Getter @Setter @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @Size(max = 40) private String name; @ManyToOne private Department department; } ``` DBをに続する際は今までに使用していないアノテーションが増えています アノテーションの解説は以下の引用タグ内に書いておきます > @Entity・・・データの入れ物(エンティティ)であることを示すアノテーション > @Id・・・エンティティの主キーであることを示すアノテーション > @GeneratedValue()・・・主キーの値を自動採番してくれるアノテーションです。 > @ManyToOne・・・多対一のテーブルを指定しています。 また、今回はモデルが2つあるのでもう1つのコードも↓におきます ``` package com.example.demo.model; import java.util.List; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import lombok.Getter; import lombok.Setter; @Getter @Setter @Entity public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @Size(max = 40) private String name; @OneToMany(mappedBy = "department") private List<Employee> employees; } ``` > @OneToMany・・・一対多のテーブルを指定します 1つ目の方には@ManyToOneがありますがこちらは@OneToManyという似ているようで違うアノテーションが記述されています これはemployee(社員クラス)からDepartment(部署テーブル)を見ると多対一の関係(部署1つに対して複数の社員がいるため)なので@ManyToOneというアノテーションを使います。 反対にDepartment(部署テーブル)からemployee(社員テーブル)を見ると一対多の関係(社員1人に対して部署は1つ)なので@OneToManyを使います。 ### リポジトリの作成 リポジトリはどちらもインターフェースクラスで作成します。 EmployeeRepository.java ``` package com.example.demo.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.example.demo.model.Employee; public interface EmployeeRepository extends JpaRepository<Employee, Long>{ } ``` DepartmentRepository.java ``` package com.example.demo.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.example.demo.model.Department; public interface DepartmentRepository extends JpaRepository<Department,Long>{ } ``` リポジトリクラスはJpaRepositoryを継承(extends)するだけで、DBの保存や取得等の便利な機能をが使えるようになるので便利です また本来はリポジトリクラスには@Repositoryというアノテーションが必要ですが、JpaRepositoryを継承する場合はアノテーションを省略することが可能です ### コントローラの作成 EmployeeController.java ``` package com.example.demo.controller; 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 com.example.demo.model.Employee; import com.example.demo.repository.EmployeeRepository; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Controller public class EmployeeController { private final EmployeeRepository repository; @GetMapping("/") public String showList(Model model) { model.addAttribute("employees",repository.findAll()); return "index"; } @GetMapping("/add") public String addEmployee(@ModelAttribute Employee employee) { return "form"; } @PostMapping("/process") public String process(@Validated @ModelAttribute Employee employee, BindingResult result) { if(result.hasErrors()) { return "form"; } repository.save(employee); return "redirect:/"; } @GetMapping("/edit/{id}") public String editEmployee(@PathVariable Long id,Model model) { model.addAttribute("employee",repository.findById(id)); return "form"; } @GetMapping("/delete/{id}") public String deleteEmployee(@PathVariable Long id) { repository.deleteById(id); return "redirect:/"; } } ``` > @PathVariable・・・パスの値を、変数に格納するアノテーション また、今回のコントローラにはrepository.から始まるのが多数あります。以前も説明しましたが簡単に言うとSQL文の代わりみたいなものです。 今回使う4種類を↓に説明載せておきます > repository.findAll()・・・データをすべて取得(SELECT * FROM テーブル名 みたいな感じ) > repository.findById(id)・・・データを一件取得します(WHEREを使った条件指定のSELECT文みたいな感じ) > repository.save(model)・・・データを保存します(UPDATE文みたいなもの) > repository.deleteById(id)・・・データを削除します(DELETE文みたいなもの) 自分はこのリポジトリを全然理解していなかったのでDBを使う際にSQL文が使えずにDBからデータ取るのにかなり苦戦しました・・・ ### 見栄え(デザイン変更) Bootstrapを使うことでデザインを簡単におしゃれなものにかえることができます これはプロジェクト直下にあるpom.xmlを編集します ![](https://i.imgur.com/ddI1dzp.png) ``` <!-- dependenciesタグ内に、以下の2つを追加 --> <dependency> <groupId>org.webjars</groupId> <artifactId>startbootstrap-sb-admin-2</artifactId> <version>4.0.6</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> <version>0.40</version> </dependency> ``` 今回もdependenciesタグ内に入れないとエラーを吐くので注意が必要です ### HTMLの編集 ここからはHTML(ページのデザイン)なのと、新しい要素も特にないので説明はなしでコードだけ載せます index.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 id="page-top"> <!--Page Wrapper --> <div id="wrapper"> <th:block th:insert="fragments/sidebar :: sidebar"></th:block> <!-- Main --> <div id="content-wrapper" class="d-flex flex-column"> <div id="content"> <div class="container-fluid"> <!-- DataTables --> <div class="card shadow my-4"> <div class="card-header py-3"> <h6 class="m-0 font-weight-bold text-primary"> 社員一覧 </h6> </div> <div class="card-body"> <div class="table-responsive"> <div th:if="${employees.size() == 0}"> 該当データがありません </div> <table class="table table-bordered" th:if="${employees.size() > 0}"> <thead> <tr> <th>#</th> <th>名前</th> <th>部署名</th> <th></th> </tr> </thead> <tbody> <tr th:each="employee : ${employees}" th:object="${employee}"> <td th:text="*{id}"></td> <td th:text="*{name}"></td> <td th:text="*{department.name}"></td> <td> <a th:href="@{/edit/{id}(id=*{id})}" class="btn btn-primary btn-icon-split"> <span class="icon text-white-50"> <i class="fas fa-trash"></i> </span> <span class="text">編集</span> </a> <a th:href="@{/delete/{id}(id=*{id})}" class="btn btn-danger btn-icon-split"> <span class="icon text-white-50"> <i class="fas fa-trash"></i> </span> <span class="text">削除</span> </a> </td> </tr> </tbody> </table> </div> </div> </div> </div> </div> </div> <!-- End of Main --> </div> <th:block th:insert="fragments/base :: scripts"></th:block> </body> </html> ``` あ、新しい要素がないかと思いましたが一応補足で > th:がついているのはThymeleafの機能を使っているということです > 例えばflagmentと書いてあるのはサイトの共通部分を外出しにするのに使います > ここら辺はあまり詳しくないのでググれば出ると思います base.html ``` <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <th:block th:fragment="header"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>社員情報アプリ</title> <!-- These are required CSS --> <link rel="stylesheet" th:href="@{/webjars/font-awesome/css/all.min.css}"> <link rel="stylesheet" th:href="@{/webjars/startbootstrap-sb-admin-2/css/sb-admin-2.min.css}"> </th:block> </head> <body> <!-- These are required JS --> <th:block th:fragment="scripts"> <script th:src="@{/webjars/jquery.min.js}"></script> <script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script> <script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script> <script th:src="@{/webjars/startbootstrap-sb-admin-2/js/sb-admin-2.min.js}"></script> <script th:src="@{/webjars/chartjs/Chart.min.js}"></script> </th:block> </body> </html> ``` sidebar.html ``` <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> </head> <body> <th:block th:fragment="sidebar"> <!-- sidebar --> <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar"> <!-- Sidebar Brand --> <a class="sidebar-brand d-flex align-items-center justify-content-center" th:href="@{/}"> <div class="sidebar-brand-icon rotate-n-15"> <i class="fas fa-id-card"></i> </div> <div class="sidebar-brand-text mx-3"> 社員情報 </div> </a> <!-- Divider --> <hr class="sidebar-divider my-0"> <!-- Nav Item --> <li class="nav-item active"> <a class="nav-link" th:href="@{/}"> <i class="fas fa-table"></i> <span>社員一覧</span> </a> </li> <li class="nav-item active"> <a class="nav-link" th:href="@{/add}"> <i class="fas fa-user-plus"></i> <span>社員登録</span> </a> </li> <!-- Divider --> <hr class="sidebar-divider d-none d-md-block"> <!-- Sidebar Toggler (Sidebar) --> <div class="text-center d-none d-md-inline"> <button class="rounded-circle border-0" id="sidebarToggle"> </button> </div> </ul> <!-- End of Sidebar --> </th:block> </body> </html> ``` form.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 id="page-top"> <!-- Page Wrapper --> <div id="wrapper"> <th:block th:insert="fragments/sidebar :: sidebar"></th:block> <!-- Main --> <div id="content-wrapper" class="d-flex flex-column"> <div id="content"> <div class="container-fluid"> <!-- DataTales --> <div class="card shadow my-4"> <div class="card-header py-3"> <h6 class="m-0 font-weight-bold text-primary"> 社員登録 </h6> </div> <div class="card-body"> <div class="table-responsive"> <form th:action="@{/process}" th:object="${employee}" method="post"> <input type="hidden" th:field="*{id}"> <div class="form-group"> <label for="name">名前</label> <input type="text" class="form-control" th:errorclass="is-invalid" th:field="*{name}"> <div class="invalid-feedback" th:errors="*{name}"> </div> </div> <div class="form-group"> <label for="department">部署名</label> <select class="form-control" th:field="*{department}"> <th:block th:each="department : ${@departmentRepository.findAll()}"> <option th:value="${department.id}" th:text="${department.name}"> </option> </th:block> </select> </div> <!-- Divider --> <hr class="sidebar-divider my-4"> <button class="btn btn-primary btn-block col-md-4"> <i class="fas fa-edit fa-fw"></i> <span class="text">保存</span> </button> </form> </div> </div> </div> </div> </div> </div> <!-- End of Main --> </div> <th:block th:insert="fragments/base :: scripts"></th:block> </body> </html> ``` ここでも新しい要素があるので補足 > ${@・・・}はSpringが管理しているオブジェクト(Bean)を参照するのに使います。 > 先ほど説明したリポジトリ等は全てBeanにあるのでこの方法で使います ``` <!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 id="page-top"> <!-- Page Wrapper--> <div id="wrapper"> <th:block th:insert="fragments/sidebar :: sidebar"></th:block> <!-- Main --> <div id="content-wrapper" class="d-flex align-items-center"> <div id="content"> <div class="container-fluid"> <!-- Error Text --> <div class="text-center"> <div class="error mx-auto" th:attr="data-text=${status}" th:text="${status}"></div> <p class="lead text-gray-800 md-5" th:text="${error}"></p> <p class="text-gray-500 md-0">ページを表示できません</p> <a th:href="@{/}">&larr; ホームに戻る</a> </div> </div> </div> </div> <!-- End of Main --> </div> <th:block th:insert="fragments/base :: scripts"></th:block> </body> </html> ``` > エラーページのerroe.htmlを作るとエラー発生時に勝手に呼び出してくれます(名前がかわるとNG)この時に${status}がエラー時のステータスコード(404や500等)、${error}にはエラーメッセージが入ります(ページを表示できません等) 最後にDBに内容を追加する時のSQL文を用意します これはいつも通りファイル>新規>その他でファイルを作成し拡張子を.sqlにします 注意なのはここからでいつも通りダブルクリックをしても開かないので右クリック>次で開く>テキストエディターを選択します ``` INSERT INTO department(name) VALUES ('営業'),('経理'),('開発'),('総務'); ``` ### ようやく実行 ここまでかなり長かったですがこれで完成です。 いつも通り実行をしてhttp://localhost:8080/ にアクセスすれば無事に動いてると思います ### まとめ DBは今の時代必ずといっていいほど使うものなので覚えておいて損はないです。 だいたいユーザー情報を入力してログインするシステムなどはDBを使って保存したり読み出しをしているのでかなりのシステムの仕組みを理解する事にも役立ちます!