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

#はじめに

前回はidに応じたTodoのbooleanの値を更新する機能を実装しました。

今回は、**「Todoの削除機能の実装」**をゴールとしています。

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>
```

#削除ボタンの追加

**完了済みを削除**というテキストでボタンをHTMLに追加します。

```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="form-area">
    <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="delete-all">
      <form method="POST" th:action="@{/deleteAll}">
        <button type="submit">完了済みを削除</button>
      </form>
    </div>
  </div>

  <div class="flex">      
    <div class="incomplete">
      <h1>未完了</h1>
      <div th:each="todo : ${allTodo}">
        <div th:if="${!todo.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>
      <div th:each="todo : ${allTodo}" th:if="${todo.done}">
        <p th:text="${todo.content}"></p>
      </div>
    </div>

  </div>  

</body>

</html>
```

```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;
}

.form-area {
  margin: 20px auto;
  display: flex;
  justify-content: center;
}

.create-todo {
  display: inline-block;
}

.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;
}

.delete-all {
  margin: 10px 10px 10px 10px;
}

.delete-all button {
  color: white;
  background-color: rgb(237, 76, 81);
  border-radius: 5px;
  font-size: 0.8em;
  cursor: pointer;
}

/* 未完了、完了済みタスクエリア */
.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;
}
```

#削除用のメソッドを作成

TodoServiceに削除用のメソッドを実装します。
`deleteAll(IterableSE<? extends T> entities)`というメソッドがCrudRepositoryにあるので、それを使用します。

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

import java.util.ArrayList;
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();
  }

  // Todoを新たにDBに登録する
  public void addTodo(Todo todo) {
    todoRepository.save(todo);
  }

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

  // 追加(Todoの全件削除)
  public void deleteAllTodo() {
    List<Todo> allTodo = todoRepository.findAll();
    List<Todo> doneList = new ArrayList<>();
    // doneがtrueかどうかを判定。trueのものをdoneListに追加する。
    for (Todo todo : allTodo) {
      if (todo.getDone()) {
        doneList.add(todo);
      }
    }
    todoRepository.deleteAll(doneList);
  }
}
```

#削除ボタン押下時の処理を追加

Controllerに、削除ボタン押下時に呼ばれるメソッドを実装します。

```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";
    }

    // エラーがなかった時は、Todoを新規登録する
    todoService.addTodo(todo);
    return "redirect:/";
  }

  // 未完了タスク横のボタンが押されたら、doneフラグをtrueに変更する
  @PostMapping("/done")
  public String doneTodo(@RequestParam(name = "id") Integer todoId) {
    Todo updateTodo = todoService.findById(todoId);
    updateTodo.setDone(true);
    todoService.addTodo(updateTodo);
    return "redirect:/";
  }

  // 追加(削除ボタンが押されたら、処理される)
  @PostMapping("/deleteAll")
  public String deleteAll() {
    todoService.deleteAllTodo();
    return "redirect:/";
  }
}
```

削除ボタンのformに`th:action="@{/deleteAll}"`としているので、
Controllerの`deleteAll()`が呼ばれ、先ほどServiceで実装した、**deleteAllTodo()**を実行します。

これで完了済みのTodoを削除します。

#動作確認

ここまで実装するとこの様になっています。

![スクリーンショット 2021-03-18 12.19.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/c6370cf4-41a6-bbdb-3950-e6b4a63f7c89.png)

**完了済みを削除**のボタンを押下すると...

![スクリーンショット 2021-03-18 12.20.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/e5e49715-4bac-42fe-2e82-4dac46fd72fe.png)

完了済みのTodoだけが削除されています！！！

#終わりに

全4回に渡って、SpringBootとMySQLを使ってCRUDの実装が完了しました。

非常にシンプルなものですが、フレームワークを用いる事でデータベースの値を画面に表示したり、更新したりなど学ぶ事が出来ました。


編集、日付を設定したりなど機能を追加したいな〜と思うので、日々勉強していこうと思います。