C#芸人が初めてSpringBootを触ってTODOアプリケーションを作るまで

# 概要
とある事情でJava(SpringBoot)のキャッチアップをすることになりました。
C#芸人なので、Javaはそんな大差ないし大丈夫だろう、と思っていたらやはり多少違うところがあったので、
一度TODOアプリケーションを作って基本的な箇所をおさらいしとこう、ということでその内容を記事にまとめておきます。
この文はC#でいうとこれです！みたいなことも書いていきます！

# 対象読者
・普段C#を書いていて、そろそろJavaも触ってみたいな、という方
・プログラミングの勉強をし始めて、そろそろJavaでWebアプリケーションでも作成してみようかな、という方

# 開発環境等
- OS: macOS
- IDE: [STS(SpringToolSuite 4)](https://spring.io/tools)
- IDE日本語化: [Pleiades プラグイン](https://mergedoc.osdn.jp/)
- Java: OpenJDK 16
- SpringBoot: 2.5.2
- TemplateEngine: Thymeleaf
- O/RM: Spring JDBC
- BuildTool: Maven
- DB: MySQL 8.0.23

# GitHub
完成形はこちらになります。
https://github.com/ryohei0109-develop/java-todo-app

# DB準備編
今回使用するDBを準備します。

```sql
CREATE USER 'user01'@'localhost' IDENTIFIED BY 'testpassword';

CREATE DATABASE tododb DEFAULT CHARACTER SET UTF8;
GRANT ALL on tododb.* to 'user01'@'localhost';

CREATE TABLE `todos`(
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `title` VARCHAR(255),
    `description` VARCHAR(255),
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

INSERT INTO `todos`(
    `title`, `description`
) VALUES
('title01', 'description01');
```

# レッツプログラミング！
それでは早速作っていきます！
STSはとても便利でデフォルトでSpringBootのテンプレートを用意してくれています！
ので、これをしっかり使わせていただきます。

STSを立ち上げ、「ファイル」→「その他」をクリック。
![スクリーンショット 2021-07-04 18.44.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/ddcc2052-482e-559c-3a1e-3a808fc423de.png)

「Spring スターター・プロジェクト」を選択し、「次へ」をクリック。
![スクリーンショット 2021-07-04 18.48.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/00e25326-e7df-1aea-94fc-2d90a414f291.png)

「名前」(プロジェクト名になります)に任意の名前を設定し、「次へ」をクリック。
![スクリーンショット 2021-07-04 18.49.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/6c2c09b5-e8cf-9a2d-eca4-bf8ac8fcd362.png)

続いて使用するプロジェクト依存関係を指定します。
C#で例えると、VisualStudioでプロジェクト作成時に使用するNugetをあらかじめ指定する、というイメージです。
この内容は「pom.xml」に記述されるので、プロジェクト作成後、手動で「pom.xml」を変更して後から追加することも可能なのでご安心下さい。
今回使用するのは以下4点です。
- Spring Web
- Thymeleaf
&nbsp;&nbsp;&nbsp;テンプレートエンジンです。
&nbsp;&nbsp;&nbsp;C#でいうとRazor等がありますね。
- JDBC API
&nbsp;&nbsp;&nbsp;O/Rマッパーです。
&nbsp;&nbsp;&nbsp;厳密にいうとそこまでやってくれないのですが、C#でいうEntityFrameworkです。
- MySQL Driver
&nbsp;&nbsp;&nbsp;MySQLとの接続を行ってくれます。
&nbsp;&nbsp;&nbsp;C#でいうMySql.Data」パッケージになります。
4つ指定ができたら「完了」をクリックします。
![スクリーンショット 2021-07-04 18.52.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/382af153-e0ed-24b8-ed05-f4e7e9ee3b29.png)

するとひとまずプロジェクトが作成されます。
が、VisualStudioと違い、この段階で実行しても特に画面は表示されません！残念！
ので、まずは画面(View)を1つ作っていきます。
SpringBootのViewは「/src/templates/」以下に配置し、拡張子は「.html」になります。
※C#の場合は「.cshtml」等になりますが、SpringBootは純粋に「.html」なのでご注意下さい！

・追加方法
プロジェクトルート→src/main/resources→templatesで右クリックをし、
「新規」→「ファイル」をクリックします。

![スクリーンショット 2021-07-04 19.51.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/62f1c8f7-df7c-cbda-e1bc-95b8bb065b79.png)

ファイル名に「index.html」と入力し、「完了」ボタンをクリックします。
※拡張子まで手入力する必要があります。

![スクリーンショット 2021-07-04 19.55.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/1149eb8e-283c-eff3-c18c-d48513247eeb.png)

index.htmlには以下内容を記述します。
「head」、「header」、「footer」部分は色んなページで使いまわしができるように記述しています。
C#でいう「_Layout.cshtml」的なアレです！

```html:index.html
<html>

<head th:replace="common/head :: head_fragment(title = 'TODO App', scripts = ~{::script}, links = ~{::link})">
	<link rel="stylesheet" type="text/css" th:href="@{/css/index.css}" />
	<script type="text/javascript" th:src="@{/scripts/index.js}"></script>
</head>

<body>
	<!-- th:replaceを使用して、 ヘッダー用のフラグメントを読込みます -->
	<header th:replace="common/header :: header_fragment"></header>

	<main class="flex-shrink-0">
		<div class="container">
			<h3>TODO App created by Spring Boot.</h3>

			<button type="button" class="btn btn-outline-dark btn-sm">Create</button>
			<table class="table">
				<thead>
					<tr>
						<th scope="col">#</th>
						<th scope="col">Title</th>
						<th scope="col">Description</th>
						<th scope="col">CreatedAt</th>
						<th scope="col">UpdatedAt</th>
						<th scope="col">Operation</th>
					</tr>
				</thead>
				<tbody>
					<tr th:each="list : ${todoList}">
						<td th:text="${list.id}"></td>
						<td>
							<a th:text="${list.title}" th:attr="href=@{/detail/{todoId}(todoId=${list.id})}"></a>
						</td>
						<td th:text="${list.description}"></td>
						<td th:text="*{#temporals.format(list.createdAt, 'yyyy-MM-dd HH:mm:ss')}"></td>
						<td th:text="*{#temporals.format(list.updatedAt, 'yyyy-MM-dd HH:mm:ss')}"></td>
						<td>
							<form action="#" th:action="@{/detail/delete}" method="post">
								<input type="hidden" name="id" th:value="${list.id}">
								<button type="submit" class="btn btn-outline-danger btn-sm">Delete</button>
							</form>
						</td>
					</tr>
				</tbody>
			</table>
		</div>
	</main>

	<!-- th:replaceを使用して、 フッター用のフラグメントを読込みます -->
	<footer th:replace="common/footer :: footer_fragment"></footer>
</body>

</html>
```

続いて部品化した「haed.html」、「header.html」、「footer.html」ファイルを作成します。
これらは/src/static/templates/common/というフォルダを作成し、その下に配置していきます。
※特にフォルダを区切らなくても問題は無いです！
![スクリーンショット 2021-07-04 20.07.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/124894b3-0302-6a3e-91c4-1328b72a2d8d.png)

「haed.html」、「header.html」、「footer.html」の中身は以下になります。
```html:head.html
<head xmlns:th="http://www.thymeleaf.org" th:fragment="head_fragment(title, scripts, links)">
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title th:text="${title}"></title>

	<!-- Bootstrap core CSS -->
	<link href=https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css rel="stylesheet">
	<!-- Custom styles -->
	<link rel="stylesheet" href="/css/common.css">
	<!-- 各ページ専用のcss -->
	<th:block th:replace="${links}" />

	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
		integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW"
		crossorigin="anonymous"></script>

	<!-- 全ページ共通のjs -->
	<script type="text/javascript" src="/scripts/base.js"></script>
	<!-- 各ページ専用のjs -->
	<th:block th:replace="${scripts}" />
</head>
```

```html:header.html
<header xmlns:th="http://www.thymeleaf.org" th:fragment="header_fragment">
	<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
		<div class="container-fluid">
			<a class="navbar-brand" href="/">TODO App - Spring Boot</a>
			<div class="collapse navbar-collapse" id="navbarCollapse">
				<ul class="navbar-nav me-auto mb-2 mb-md-0">
					<li class="nav-item active">
						<a class="nav-link" aria-current="page" target="_blank" href="https://github.com/ryohei0109-develop/java-todo-app">GitHub</a>
					</li>
				</ul>
			</div>
		</div>
	</nav>
</header>
```

```html:footer.html
<footer xmlns:th="http://www.thymeleaf.org" th:fragment="footer_fragment" id="footer"
	class="footer mt-auto py-3 bg-light">
	<div class="container">
		<span class="text-muted">footer</span>
	</div>
</footer>
```

続いてJavaScript, CSSファイルも作成していきます。
これらは「/src/main/resources/static/」以下に配置します。
htmlファイルからJavaScript, CSSファイル等を呼ぶ時は、staticフォルダがルート扱いになります。

今回は学習用もかねてるので、JavaScriptファイルの中身は空にしていますが、
今後開発する際、JavaScriptや個別のCSSはこんな感じで配置していけば良いのか、ふんふん、というくらいに思っておいて下さい！
![スクリーンショット 2021-07-04 20.15.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/28c31449-cb9b-7c0c-9b94-d92615dccd98.png)

common.cssは全ページから呼ばれる共通CSSになります。
base.jsは全ページから呼ばれる共通JavaScriptになります。

index.js及びindex.csssはindex.htmlからのみ呼ばれようにしています。

```css:common.css
main > .container {
  padding: 60px 15px 0;
}

.bd-placeholder-img {
  font-size: 1.125rem;
  text-anchor: middle;
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
}

@media (min-width: 768px) {
  .bd-placeholder-img-lg {
    font-size: 3.5rem;
  }
}

h1 {
  margin-bottom: 2rem;
}

.btn-sm {
  font-size: 0.6rem;
}
```

ここまでで http://localhost:xxxx/index.html のページの作成が完了している状態です。
続いてTODO詳細画面( http://localhost:xxxx/detail.html )を作っていきます。

「/src/main/resrouces/templates/」に「detail.html」を追加します。
![スクリーンショット 2021-07-04 20.44.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/362c7be5-2912-6417-da3a-6992319a8add.png)

中身はこちらになります。

```html:detail.html
<html>

<head th:replace="common/head :: head_fragment(title = 'TODO App', scripts = ~{::script}, links = ~{::link})">
	<link rel="stylesheet" type="text/css" th:href="@{/css/detail.css}" />
	<script type="text/javascript" th:src="@{/scripts/detail.js}"></script>
</head>

<body>
	<!-- th:replaceを使用して、 ヘッダー用のフラグメントを読込みます -->
	<header th:replace="common/header :: header_fragment"></header>

	<main class="flex-shrink-0">
		<div class="container">
			<h1>詳細</h1>
			<form action="#" th:action="@{/detail/save}" th:object="${todo}" method="post">
				<div class="mb-3">
					<label class="form-label">Title</label>
					<input type="text" name="title" class="form-control" th:value="${todo} ? ${todo.title} : ''">
				</div>
				<div class="mb-3">
					<label class="form-label">Description</label>
					<input type="text" name="description" class="form-control"
						th:value="${todo} ? ${todo.description} : ''">
				</div>
				<input type="hidden" name="id" th:value="${todo} ? ${todo.id} : ''">
				<input type="submit" value="Submit" class="btn btn-primary" />
			</form>
		</div>
	</main>

	<!-- th:replaceを使用して、 フッター用のフラグメントを読込みます -->
	<footer th:replace="common/footer :: footer_fragment"></footer>
</body>

</html>
```

ついでにdetail.html用の「detail.css」、「detail.js」ファイルも追加しておきます。
![スクリーンショット 2021-07-04 20.46.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/93c58155-9616-4685-135b-742e762594b4.png)


ここまででMVCのViewの作成は完了しました！やりました！
続いてController部分を作っていきます。
Controller部分でルーティングも行い、先程作成したindex.htmlやdetail.htmlの呼び出しを行います。
なので、「 http://localhost:xxxx/index.html 」等ではなく、
「 http://localhost:xxxx/index 」でアクセスできるようになります。

Controllerファイルは特に配置する場所に指定はありませんが、
今回は「/src/main/java/com.todo.pkg.controller」というパッケージを作り、その下に作っていきたいと思います。
ちなみに「パッケージ」というのは、C#でいう「namespace(名前空間)」になります。
ただし、C#は「hoge.piyo.fuga」という名前空間を指定しても、
実際のフォルダ階層は「hoge」フォルダ直下でも特に怒られませんが、
Javaの場合、「パッケージ」と実際のフォルダ階層は合わせる必要があるのでご注意下さい。

まずはパッケージを追加していきます。

![スクリーンショット 2021-07-04 20.28.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/e422a022-1504-cc32-e3ee-fc97be7085b1.png)

パッケージ名に「com.todo.pkg.controller」を入力し、「完了」をクリックします。
![スクリーンショット 2021-07-04 20.29.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/8ef9bae6-776d-1721-159a-26d7f4472a9a.png)

パッケージの作成が完了したら、続いてクラスを作っていきます。

作成したパッケージ上で右クリック→新規→クラスをクリックします。
![スクリーンショット 2021-07-04 20.30.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/22389335-4baf-396a-940e-519b8c7818a7.png)

名前に「IndexController」と入力し、「完了」ボタンをクリックします。
![スクリーンショット 2021-07-04 20.32.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/827031a9-3307-31e4-56dc-b020b8e47bff.png)

IndexController.javaではindexのルーティングを行うように設定します。

```java:IndexController.java
package com.todo.pkg.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.todo.pkg.model.Todo;
import com.todo.pkg.model.TodoRepository;

@RequestMapping("/")
@Controller
public class IndexController {
	@Autowired
	private TodoRepository todoRepository;

	@GetMapping()
	public String view(Model model) {
		try {
			List<Todo> list = todoRepository.getAll();

			model.addAttribute("todoList", list);

		} catch (Exception ex) {
			System.out.println(ex.getMessage());
		}

		return "index";
	}
}
```

少しだけ細くすると、

```java
@RequestMapping("/")
```
部分でlocalhost:xxxx/アクセス時のルーティングを行います。
そして最後の

```java
return "index";
```
この部分でindex.htmlを返す、というような命令になっています。

続いてdetail.html用のControllerも作っていきます。

IndexController同様、「com.todo.pkg.controller」直下に配置していきます。

![スクリーンショット 2021-07-04 20.50.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/7485712b-f16d-2a33-1731-24d231d74392.png)

中身はこちらになります。

```java:DetailController.java
package com.todo.pkg.controller;

import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.todo.pkg.model.Todo;
import com.todo.pkg.model.TodoRepository;

@RequestMapping("/detail")
@Controller
public class DetailController {
	@Autowired
	private TodoRepository todoRepository;

	/**
	 * /Detail/{id}
	 * 
	 * @param model
	 * @param id
	 * @return
	 */
	@GetMapping("/{id}")
	public String view(Model model, @PathVariable int id) {
		try {
			if (id > 0) {
				Todo todo = todoRepository.getTodoById(id);

				model.addAttribute("todo", todo);
			}
		} catch (Exception ex) {
			System.out.println(ex.getMessage());
		}

		return "detail";
	}

	@PostMapping("/save")
	public String save(@ModelAttribute Todo todo, Model model) {
		todoRepository.save(todo);

		return "redirect:/";
	}
	
	@PostMapping("/delete")
	public String delete(@RequestParam int id, Model model) {
		todoRepository.delete(id);

		return "redirect:/";
	}
}

```

これでMVCのControllerは完了です！

最後にModelを作っていきましょう！
こちらもController同様、パッケージを作っていきます。

![スクリーンショット 2021-07-04 20.57.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/3675d668-6d28-87f8-aca7-5075f5e2d34c.png)

今回は`todos`テーブルをモデリングするので、ファイル名は「Todo.java」として作っていきます。
![スクリーンショット 2021-07-04 20.59.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/99d756a1-9195-a4d7-ea5b-af456f532fd1.png)

中身はこちらです。

```java:Todo.java
package com.todo.pkg.model;

import java.time.LocalDateTime;
import java.util.Map;

public class Todo {
	public Todo() {

	}

	public Todo(Map<String, Object> result) {
		this.id = (int) result.get("id");
		this.title = (String) result.get("title");
		this.description = (String) result.get("description");
		this.createdAt = (LocalDateTime) result.get("created_at");
		this.updatedAt = (LocalDateTime) result.get("updated_at");
	}

	private int id;
	private String title;
	private String description;
	private LocalDateTime createdAt;
	private LocalDateTime updatedAt;

	public int getId() {
		return this.id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getTitle() {
		return this.title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getDescription() {
		return this.description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public LocalDateTime getCreatedAt() {
		return this.createdAt;
	}

	public void setCreatedAt(LocalDateTime createdAt) {
		this.createdAt = createdAt;
	}

	public LocalDateTime getUpdatedAt() {
		return this.updatedAt;
	}

	public void setUpdatedAt(LocalDateTime updatedAt) {
		this.updatedAt = updatedAt;
	}
}
```

モデルの作成が完了したら、Repositoryクラスも作っていきます。
![スクリーンショット 2021-07-04 21.01.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/50385fc1-d64a-1742-8ef0-d918321c0a70.png)

中身はこちらです。

```java:TodoRepository.java
package com.todo.pkg.model;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class TodoRepository {
	private final JdbcTemplate jdbcTemplate;

	@Autowired
	public TodoRepository(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	public Todo getTodoById(int id) {
		String sql = "SELECT id,title,description,created_at,updated_at FROM `todos` WHERE `id` = ?";
		Map<String, Object> result = jdbcTemplate.queryForMap(sql, id);

		return new Todo(result);
	}

	public List<Todo> getAll() {
		String sql = "SELECT id,title,description,created_at,updated_at FROM `todos`";
		List<Map<String, Object>> todoList = jdbcTemplate.queryForList(sql);
		List<Todo> list = new ArrayList<>();
		for (Map<String, Object> result : todoList) {
			list.add(new Todo(result));
		}

		return list;
	}

	public void save(Todo todo) {
		if (todo.getId() > 0) {
			update(todo);
		} else {
			insert(todo);
		}
	}

	private void insert(Todo todo) {
		jdbcTemplate.update("INSERT INTO `todos`(`title`,`description`) Values(?,?)", todo.getTitle(),
				todo.getDescription());
	}

	private void update(Todo todo) {
		jdbcTemplate.update("UPDATE `todos` SET `title` = ?, `description` = ? WHERE `id` = ?", todo.getTitle(),
				todo.getDescription(), todo.getId());
	}

	public void delete(int id) {
		jdbcTemplate.update("DELETE FROM `todos` WHERE `id` = ?", id);
	}
}

```

そして最後に「/src/main/resources/application.properties」を編集していきます。
まずは中身を書きます。

```
pserver.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/tododb
spring.datasource.username=user01
spring.datasource.password=testpassword
```

`pserver.port=8080`は起動するWebサーバのポート番号を指定します。
この場合、「 http://localhost:8080/ 」でアクセスできます。

`spring.datasource`はDB接続周りの設定になります。

以上で設定完了になります！
それでは実行していきます！

「 http://localhost:8080/ 」
![スクリーンショット 2021-07-04 21.27.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/513dd635-88a8-50e1-b904-0238af0977ef.png)

「 http://localhost:8080/Detail/ 」
![スクリーンショット 2021-07-04 21.36.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694199/96607905-e423-3a01-5056-69f1bc3cd65d.png)

表示されました！やった！
お疲れ様でした！