SpringBoot + MySQL でTodoリストを作る！CRUDの実装(Update編)

#はじめに

前回はフォームを用意してTodoの新規登録機能を実装しました。

今回は、**「blooeanの値を変更してMySQLのデータを更新する」**をゴールとしています。

SpringBootを学び始めた方、復習をしたい方に向けて学んだ事を共有します。

##開発環境

```
OS: macOS Mojave(バージョン 10.14.6)
```

```
$ java -version
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
```

```
$ mvn -version
Maven home: /usr/local/Cellar/maven/3.6.3_1/libexec
Java version: 15.0.2, vendor: N/A, runtime: /usr/local/Cellar/openjdk/15.0.2/libexec/openjdk.jdk/Contents/Home
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.14.6", arch: "x86_64", family: "mac"
```

```
$ spring --version
Spring CLI v2.3.1.RELEASE
```

```
$ mysql --version
mysql  Ver 8.0.23 for osx10.14 on x86_64 (Homebrew)
```

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>todolist</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>todolist</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	
	<dependencies>

		<!-- テンプレートエンジン -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<!-- RESTfulを含んだWebアプリケーションライブラリの利用 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- ORMのMyBatis -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.4</version>
		</dependency>

		<!-- ファイルの変更を検知すると自動で再起動する -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>

		<!-- MySQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<!-- アノテーションを付与する事でgetter,setter等を自動生成してくれる -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<!-- テスト用 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- DBとJavaオブジェクトやり取りを行う。 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<!-- バリデーション用 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>
```

#booleanの値によって表示する領域を分ける

まずhtmlを編集します。
`th:if="${!todo.done}"`は、
todoのdoneの値が**true/false**どちらか判別して、表示するかどうかを決めています。

```html:home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <link rel="stylesheet" type="text/css" th:href="@{css/home.css}">
  <title>SpringTodoList</title>
</head>
<body>

  <div class="create-todo">
    <form method="POST" th:action="@{/}" th:object="${todo}">
      <p th:errors="*{content}" class="todo-error-message"></p>
      <input type="text" th:field="*{content}">
      <button type="submit">登録</button>
    </form>
  </div>


  <!-- 今回の変更点 -->
  <div class="flex">      
    <div class="incomplete">
      <h1>未完了</h1>
      <div th:each="todo : ${allTodo}">
        <!-- doneのフラグがfalseのものを表示 -->
        <div th:if="${!todo.done}">
          <!-- Controllerの @PostMapping(/done) と結びつける -->
          <form method="POST" th:action="@{/done}">
            <input type="hidden" name="id" th:value="${todo.id}" />
            <button type="submit"></button>
          </form>
          <p th:text="${todo.content}"></p>
        </div>
      </div>
    </div>

    <div class="complete">
      <h1>完了済み</h1>
      <!-- doneのフラグがtrueのものを表示 -->
      <div th:each="todo : ${allTodo}" th:if="${todo.done}">
        <p th:text="${todo.content}"></p>
      </div>
    </div>

  </div>
  <!-- ここまで -->
  
</body>

</html>
```

ここで丸いボタンを作成して、ボタンが押下されたらControllerの処理が走るようにします。
Todoのidを渡しているので、そのidbooleanの値を変更してDBを更新させる予定です。

```html
<!-- Controllerの @PostMapping(/done) と結びつける -->
<form method="POST" th:action="@{/done}">
  <input type="hidden" name="id" th:value="${todo.id}" />
  <button type="submit"></button>
</form>
<p th:text="${todo.content}"></p>
```

staticフォルダにcssフォルダを作成して、その中にhome.cssを作成してレイアウトを整えます。

```css:home.css
body {
  padding: 0;
  margin: 0;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 1.4rem;
}

h1 {
  text-align: center;
  margin: 20px 0 0 0;
}

p {
  display: inline-block;
}

.create-todo {
  margin: 20px 0 0 0;
  display: flex;
  justify-content: center;
}

.create-todo input {
  font-size: 1em;
  line-height: 1.2;
  border-width: 2px;
  border-style: solid;
  border-radius: 5px;
  padding: 0.5em 0.5em;
  width: 300px;
}

.create-todo button {
  font-size: 1em;
  line-height: 1.2;
  border-width: 2px;
  border-style: solid;
  border-radius: 5px;
  background-color: #008CBA;
  cursor: pointer;
  color: white;
  display: inline-flex;
  justify-content: center; /* コンテンツを水平方向に中央揃え */
  align-items: center; /* コンテンツを垂直方向に中央揃え */
  padding: 0.5em 1.2em;
}

.todo-error-message {
  color: red;
  text-align: center;
  font-size: 1.2rem;
}

/* 未完了、完了済みタスクエリア */
.flex {
  display: flex;
  justify-content: space-around;
  margin: 30px 0 0 0;
}

.incomplete {
  text-align: center;
  width: 400px;
  background-color: pink;
}

.incomplete form {
  display: inline;
}

.incomplete button {
  background-color: white;
  border-radius: 50%;
  height: 15px;
  width: 15px;
  cursor: pointer;
  display: inline-flex;
  justify-content: center; /* コンテンツを水平方向に中央揃え */
  align-items: center; /* コンテンツを垂直方向に中央揃え */
}

.complete {
  text-align: center;
  width: 400px;
  background-color: green;
}
```

#Boolean型のgetter,setterを用意する

`Lombok`は`@Data`アノテーションを付与することで、自動でgetter,setterを作成してくれます。
ただ**Booleanの型の変数は、get〇〇,set〇〇**と出来ません。


##lombok.configを作成する


一番上の階層に`lombok.config`というファイルを作成し以下の様に記述します。

![スクリーンショット 2021-03-17 13.16.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/0d08c1f0-5e6a-96e5-9d73-2519c49b72c9.png)


```text:lombok.config
lombok.getter.noIsPrefix=true
```

これで`is〇〇`でBooleanの変数を取得するようになっていましたが、`get〇〇,set〇〇`が使えるようになります。

[Lombokの公式](https://projectlombok.org/features/GetterSetter)

#findByIdメソッドの実装

idに応じたTodoを取得したいので、**findById()**メソッドを実装します。

```java:TodoService.java
package com.example.todolist.Service;

import java.util.List;
import java.util.Optional;

import com.example.todolist.Model.Todo;
import com.example.todolist.Repository.TodoRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

// DBとの具体的な処理(データの取得、新規作成など)を記述するクラス
@Service
public class TodoService {
  
  @Autowired
  TodoRepository todoRepository;

  public List<Todo> searchAll() {
    return todoRepository.findAll();
  }

  public void addTodo(Todo todo) {
    todoRepository.save(todo);
  }

  // 追加(idに応じたTodoを返却する)
  public Todo findById(Integer id) {
    Optional<Todo> updateTodo = todoRepository.findById(id);
    return updateTodo.get();
  }
}
```

**findById()**メソッドは、`Optional`クラスで受け取らなければいけないためそのように受け取り、
中のTodoを取り出すには、**get()**メソッドを使用しています。

[公式の説明](https://spring.pleiades.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html#findById-ID-)


#Controllerでupdate用メソッドの実装

Viewからidを受け取り先ほど実装したfindByIdメソッドを使用して、特定のデータを更新する処理を実装していきます。

```java:TodoController.java
package com.example.todolist.Controller;

import java.util.List;

import javax.validation.Valid;

import com.example.todolist.Model.Todo;
import com.example.todolist.Service.TodoService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;


@Controller
public class TodoController {

  @Autowired
  TodoService todoService;
  
  @GetMapping("/")
  public String home(Model model) {
    List<Todo> allTodo = todoService.searchAll();
    model.addAttribute("allTodo", allTodo);
    model.addAttribute("todo", new Todo());
    return "home";
  }

  @PostMapping("/")
  public String createTodo(@Valid Todo todo, BindingResult bindingResult,Model model) {

    if(bindingResult.hasErrors()) {
      List<Todo> allTodo = todoService.searchAll();
      model.addAttribute("allTodo", allTodo);
      model.addAttribute("todo", todo);
      return "home";
    }

    todoService.addTodo(todo);
    return "redirect:/";
  }

  // 追加(未完了タスク一覧にある丸いボタンが押されたら、この処理が実行される)
  @PostMapping("/done")
  public String doneTodo(@RequestParam(name = "id") Integer todoId) {
    Todo updateTodo = todoService.findById(todoId);
    updateTodo.setDone(true);
    todoService.addTodo(updateTodo);
    return "redirect:/";
  }

}
```

#動作確認

現在、CSSを適用させたのでこのようになっています。
![スクリーンショット 2021-03-17 14.21.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/b1692a71-64d0-5698-3f88-87243f38ea57.png)

例えば、**「猫のトイレ掃除」**の横のボタンを押します。

![スクリーンショット 2021-03-17 14.22.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/46f620e3-d24c-a4ba-f9db-ae3ecdd21e9c.png)

完了済みのエリアに移動しました！！！


#終わりに

idに応じたデータを更新する実装も無事出来ました。
SpringBootにはメソッドを使用するだけで簡単にDBの値を更新する事ができる事が分かりました。

最後は削除機能を実装しようと思います。