Spring Bootで簡単なWebアプリを作ってみたい！【CRUD処理を実装】

# 【はじめに】
この記事は[【マイスター・ギルド】本物の Advent Calendar 2021](https://adventar.org/calendars/6579)の12日目の記事です。
ためになる技術的な記事から、猫ちゃんに癒やされる記事、健康オタクになれる記事まで、バラエティに富んだ記事が投稿されているので、ぜひチェックしてみてください！

# 【経緯】
こちらの記事は前記事の[Spring Bootで簡単なWebアプリを作ってみたい！【準備からGet・Post表示まで】](https://qiita.com/takuch/items/3dc30caf45af06a01f41)の続きになります。
Javaの入門書である「スッキリわかるJava入門」を読み終わったので、なにかWebアプリを作ってみたいと思ったのがきっかけです。
ちょうど業務でSpring Bootが使われていたので勉強を始めたのですが、チュートリアルが少なく苦労しているので、備忘録的にまとめようと思っています。
この記事だけで、CRUDの実装はできるようになっていますが、前記事で説明した内容は省略しています。
不明点があった場合は前記事も参照していただければと思います。

# 【対象読者】
Javaの入門書を読み終わって、これからWebアプリを作ってみたいと思っているひと向けです。
説明がわかりにくかったり、間違っている部分があれば、コメントいただければありがたいです。　


# 【この記事でやること】
ToDoアプリを題材にSpring BootでCRUD処理を実装します。

## ■テストアプリケーションのイメージ
最小限の機能でToDoアプリを作成します。
下記に動作イメージを示します。
![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2157900/2bf4b652-ab7f-5da6-e920-317e0a75ca5b.gif)


# 【開発環境】
- OS: Windows 10 Pro 
- IDE: IntelliJ IDEA 2021.2.2(Community Edition)
- Java: openjdk 11.0.2 2019-01-15
- Spring Boot: 2.6.1
- MySQL: 8.0.26

# 【準備編】
## ■MySQLのインストール
下記を参考にMySQLのインストールとユーザー登録、動作確認までします。
サイトに詳しく手順が書いてあるので、ここでは説明を省きます。
ユーザー名とパスワードは後ほど使うので、メモしておきます。

- MySQLのインストールで参考にしたサイト  
Windows: https://prog-8.com/docs/mysql-env-win  
Mac: https://prog-8.com/docs/mysql-env

- MySQLのDL元  
Windows: https://downloads.mysql.com/archives/installer/

# 【実装編】
## ■ToDoアプリを作成して、CRUDしてみよう（MySQL）
### - Spring initializrでプロジェクトを作成
今回も[Spring initializr](https://start.spring.io/)でプロジェクトを用意します。
設定を下記のようにして、GENERATEします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2157900/7ed9a01a-b959-6787-62d2-500dcb9e77f6.png)

- Dependencies
    - Lombok : アノテーションを付けるだけで、GetterやSetterを自動で生成してくれるライブラリ
    - Spring Data JPA : 簡単にDBアクセスができるライブラリ
    - MySQL Driver : MySQLとの接続のためのドライバー（だと思われる）　　

GENERATEしたzipを解凍してIDEで起動します。


### - Modelの作成

`src\main\java`の`com.example.SpringBootTodo`に`model`パッケージを作成し、`Todo.java`を作成します。

```java:Todo.java
package com.example.SpringBootTodo.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Getter // フィールドのgetメソッド(id, content)を自動生成する
@Setter // フィールドのsetメソッド(id, content)を自動生成する
@Entity // データの入れ物であるEntityクラスであることを指定する
public class Todo {
  @Id // Entityの主キーを設定する
  @GeneratedValue // Entityの値を自動採番する
  private Long id;
  private String content;
}
```
- モデルは、DBテーブルのカラム名をイメージしながらフィールドとそれぞれのGetter、Setterを用意します
    - Lombokを使えば、`@Getter`、`@Setter`アノテーションを付けるだけで、自動生成してくれます
- Entityクラスであること示すために`@Entity`アノテーションをつけます
    - Entityクラスでモデルを作成することで、クラス名と同じ名前のテーブルをMySQLに作成してくれます
- `id`フィールドには`@Id`、`@GenerateValue`アノテーションを付けておくと、主キー設定され、自動採番されるようになります

### - Repositoryの作成
`com.example.SpringBootTodo`に`repository`パッケージを作成し、`TodoRepository.java`を作成します。

```java:TodoRepository.java
package com.example.SpringBootTodo.repository;

import com.example.SpringBootTodo.model.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository // Repositoryクラスであることを示す
public interface TodoRepository extends JpaRepository<Todo, Long> { 

}
```
- Repositoryインターフェースを作成して`JpaRepository`を継承するだけで、DBのレコード取得や保存などの便利な機能を使えるようになります
    - `JpaRepository<Entityクラス名, IDの型>`で継承します
- 各Entityクラス毎に作成します

### - Controllerの作成
`com.example.SpringBootTodo`に`controller`パッケージを作成し、`TodoController.java`を作成します。

```java:TodoController.java
package com.example.SpringBootTodo.controller;

import com.example.SpringBootTodo.model.Todo;
import com.example.SpringBootTodo.repository.TodoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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;

@RequiredArgsConstructor
@Controller
public class TodoController {

  private final TodoRepository repository;

  //  Read
  @GetMapping("/")
  public String showAllTodo(@ModelAttribute Todo todo, Model model) {
    model.addAttribute("todos", repository.findAll()); 
    return "index";
  }

  //  Create
  @PostMapping("/add")
  public String addTodo(@ModelAttribute Todo todo) {
    repository.save(todo);
    return "redirect:/";
  }

  //  Update
  @PostMapping("/edit")
  public String editTodo(@ModelAttribute Todo todo) {
    repository.save(todo);
    return "redirect:/";
  }

  //  Delete
  @GetMapping("/delete/{id}")
  public String deleteTodo(@PathVariable Long id) { 
    repository.deleteById(id);
    return "redirect:/";
  }
}
```
- Controllerクラスには、CRUDのそれぞれのメソッドを用意しています
- フィールドには先程作成したRepositoryを用意します
- `@RequiredArgsConstructor`アノテーションをつけることで、下記のようなコンストラクタインジェクションを自動生成してくれます

```java
//省略
@Controller
public class TestController {
  
  private final TodoRepository repository;
  
  public TestController(TodoRepository repository){
    this.repository = repository;
  }
//省略
}
```
CRUDそれぞれのメソッドを説明します。

#### Read

```java
  //  Read
  @GetMapping("/")
  public String showAllTodo(@ModelAttribute Todo todo, Model model) {
    model.addAttribute("todos", repository.findAll()); 
    return "index";
  }
```
- `@ModelAttribute Todo todo`でJava側の変数`todo`とThymeleafの変数`${todo}`とを紐付けます
    - Thymeleafのコードはこの後に説明しているので、合わせて見ていただければと思います
- `repository.findAll()`でDBのtodoテーブルからすべてのデータを取り出します

#### Create

```java
  //  Create
  @PostMapping("/add")
  public String addTodo(@ModelAttribute Todo todo) {
    repository.save(todo); 
    return "redirect:/";
  }
```
- リクエストで受け取った値を`repository.save(todo)`でtodoテーブルに保存します
    - saveの引数にはEntity(ここではtodo）を指定します
- `return "redirect:/"`は、`redirect:`のあとに指定したルートを再度呼び出します
    - 今回は"/"なので、再度Readの処理が実行されます

#### Update

```java
  //  Update
  @PostMapping("/edit")
  public String editTodo(@ModelAttribute Todo todo) {
    repository.save(todo);
    return "redirect:/";
  }
```
- Createと全く同じメソッドで実装可能です
    - なぜ同じメソッドで異なる処理が可能かというと、リクエストでidが送られてくるかどうかで挙動が異なるからです
    - Createの場合はEntity(ここでいうtodo)のメンバ変数idはnullで送られてくるため、バインドした際にidが自動採番されて新規に登録されます
    - Updateの場合はEntityのidが指定された状態で送られてくるため、そのidに対応するレコードの修正をするようになっています
    - 今回は説明のためにCreateとUpdateで別のメソッドを用意しましたが、Post先を同じにすれば一つのメソッドで実現可能です

#### Delete

```java
  //  Delete
  @GetMapping("/delete/{id}")
  public String deleteTodo(@PathVariable Long id) {
    repository.deleteById(id);
    return "redirect:/";
  }
```
- `@GetMapping("/delete/{id}")`の`{id}`でURLにid値を設定することができます
- `@PathVariable Long id`でURLの`{id}`の値が変数`id`に代入されます
- `repository.deleteById(id)`で指定したid値のレコードを削除します
    - ここはEntityを指定するのではなくidを指定します
- repositoryのメソッドはここで使ったもの以外にもあるので、興味がある方は[公式リファレンス](https://spring.pleiades.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html)を参照ください

### - Thymeleafテンプレートの作成
`src\main\resources\templates`に`index.html`を作成します。

```HTML:index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>ToDoApp</title>
</head>
<body>
<h2>ToDo新規追加</h2>
<!--Create-->
<form th:action="@{/add}" th:object="${todo}" method="post">
  <label for="content">ToDo:</label>
  <input type="text" th:field="*{content}">
  <button>登録</button>
</form>

<br>
<h2>ToDo一覧</h2>
<div th:if="${todos.size() == 0}">
  タスクがありません
</div>
<div th:if="${todos.size() > 0}">
  <!--Read & Edit-->
  <div th:each="todo : ${todos}"> <!--th:each 繰り返し処理をおこなう-->
    <form th:action="@{/edit}" th:object="${todo}" method="post" style="display: inline">
      <input type="hidden" name="id" th:value="*{id}">
      <label for="content">[[*{id}]] : </label>
      <input type="text" name="content" th:value="*{content}">
      <button>更新</button>
    </form>
    <!--Delete-->
    <a th:href="@{/delete/{id}(id=${todo.id})}">削除</a>
  </div>
</div>
</body>
</html>
```
- `th:object`でフォームにバインドするオブジェクトを指定します
    - 今回は、Controllerで指定した`todo`を設定しています
- `th:field`は`th:object`で指定したオブジェクトのフィールド名を指定します
- `th:if`でif文を作成することができます
- `th:each`で繰り返し処理を行うことができます。拡張for文のような記述をしています
    - `${todos}`はControllerのReadの`odel.addAttribute("todos", repository.findAll())`で指定した`"todos"`と紐付いています
- `[[]]`でモデルの値を表示させることができます
- `<a th:href="@{/delete/{id}(id=${todo.id})}">`で`{id}`に`todo.id`を値を代入したリンクを作成できます

### - DBの作成
MySQLのDBの作成をします。
下記サイトの「1.データベースの作成」を参照して、好きな名前でDBを作成します。
ここではDB名を`spring_boot_todo`としました。

https://prog-8.com/docs/mysql-database-setup

### - propertiesの修正
`src\main\resources`の`application.properties`を修正します。

```properties:application.properties
# DBの接続先情報
spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_todo
spring.datasource.username=root
spring.datasource.password=root

spring.jpa.hibernate.ddl-auto=create
```
- `jdbc:mysql://localhost:3306/spring_boot_todo`の最後の部分は先程作成したDB名を指定します
- `spring.datasource.username=`と`spring.datasource.password=`はMySQLのインストールで作成したアカウントを指定します
- `spring.jpa.hibernate.ddl-auto=create`はspring bootの起動のたびにDBをどうするかを指定します
    - createは実行の度に既存のテーブルを削除して新たに作成します

### - 動作確認
プログラムを実行して、`http://localhost:8080`にアクセスすると下のような画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2157900/a0703838-1b27-3b5e-b13b-5fa213cb8fbf.png)
新規追加のFormにToDoを入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2157900/cff1e7bf-2412-a06a-3783-cb0df91d2c2b.png)
登録ボタンを押すと、ToDo一覧に先程の内容が追加されます。（Create & Read）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2157900/da1036d4-eee8-b45c-a60e-08982bf8e6a2.png)
一覧のフォームの内容を修正して更新ボタンを押すと、内容が更新されます。（Update）
わかりにくいですが、ブラウザの更新ボタンを押しても更新後の内容が表示されていれば正しく動作しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2157900/7ddd5855-0cde-d71b-6de4-cb27350d78ee.png)
ToDo一覧の削除リンクを押すと一覧から消えます。（Delete）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2157900/134b32f2-5f81-bceb-93c1-3174e0f53807.png)


以上で実装完了です！

# 【まとめ】
今回はSpring BootでToDoアプリを題材にCRUD処理の実装を試してみました。

次回は今回実装したToDoアプリをRestAPIにしたいと思っています。
（フロント側の学習も必要になるので、いつ投稿できるかわかりませんが、ご期待ください。）

# 【参考文献】
- [Spring Boot公式ガイド][1]
- [Spring Boot公式 : JPA で MySQL データアクセス][1-1]
- [SpringBootに入門するための助走本（Zenn改訂版）][2]
- [Spring Boot 2 入門：基礎から実演まで][3]

[1]: https://spring.pleiades.io/projects/spring-boot "Spring Boot公式"
[1-1]: https://spring.pleiades.io/guides/gs/accessing-data-mysql/ "JPA で MySQL データアクセス"
[2]: https://zenn.dev/sugaryo/books/spring-boot-run-up "SpringBootに入門するための助走本（Zenn改訂版）"
[3]: https://www.amazon.co.jp/Spring-Boot-2-%E5%85%A5%E9%96%80-%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E5%AE%9F%E6%BC%94%E3%81%BE%E3%81%A7-ebook/dp/B0893LQ5KY "Spring Boot 2 入門：基礎から実演まで"