Spring Bootでアプリを作成してみた【課題管理システム】

[JetBrains Toolbox App](https://www.jetbrains.com/ja-jp/toolbox-app/)　をインストールする


## 1.プロジェクト作成

公式：[spring initializr](https://start.spring.io/)

① ブラウザを開き、(https://start.spring.io/) を入力し、開く。
② 下記を入力し、Dependenciesで下記を選択し、『GENERATE』ボタンを押す。

- Project: Gradle
- Language: Java
- SpringBoot: 3.1.0
- Group: com.example
- Name : its
- Artifact: its
- Description: Issue Tracking System
- Package name: com.example

Dependencies

- Spring Web
- Thymeleaf
- Spring dev tools

## 2.トップページを表示する

Webアプリケーションを実装するとは
1. リクエストとControllerを紐付ける
2. Controllerでレスポンスを生成する 

①src > main > java > com > example > <プロジェクト名> 直下にwebフォルダを作成し、IndexController.javaファイルを作成する。

```web / IndexController.java
package com.example.its.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody

@Controller
public class IndexController {

    @GetMapping("/")
    @ResponseBody
    public String index() {
        return "<h1>Hello, World!!</h1>";
    }
}
```
## 3.テンプレートエンジンを利用する

テンプレートエンジンとは、テンプレートとデータから動的に文字を生成する仕組みのこと

①spring initializrに『Thymeleaf』を追加して、『EXPLORE』を押し、下記のファイルにコードを加える。

```build.gradle
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
```

②src > main > resource > templete にindex.htmlファイルを作成する。

```src/main/resources/templates/index.html
<!DOCTYPE >
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>課題管理アプリケーション</title>
  </head>
  <body>
    <h1>Assignment management app</h1>
  </body>
</html>
```

③IndexController.javaファイルを編集する。

```web/IndexController.java
package com.example.its.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

    @GetMapping("/")
    public String index() {
        return "index";
    }
}
```
## 4.Spring Dev Toolsを導入して開発効率を上げる

①spring initializrに『Spring dev Tools』を追加して、『EXPLORE』を押し、下記のファイルにコードを加える。

```build.gradle
developmentOnly 'org.springframework.boot:spring-boot-devtools'
```
②main/resouces/application.propertiesに下記のコードを加える。

```main/resouces/application.properties
spring.thymeleaf.prefix=file:src/main/resources/templates/
```

## 5.modelとcontrollerを作成する

①src > main > java > com > example > <プロジェクト名> 直下に
domainissueフォルダを作成し、IssueEntityファイルを作成する。

```domainissue/IssueEntity.java
package com.example.its.domainissue;

public class IssueEntity {
    private long id;
    private String summary;
    private String description;

    public IssueEntity(long id, String summary, String description) {
        this.id = id;
        this.summary = summary;
        this.description = description;
    }

    public long getId() {
        return id;
    }

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

    public String getSummary() {
        return summary;
    }

    public void setSummry(String summary) {
        this.summary = summary;
    }

    public String getDescription() {
        return summary;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}
```
②webにissueフォルダを作成し、IssueController.javaファイルを作成する。

```issue/IssueController.java
package com.example.its.web.issue;

import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.thymeleaf.engine.AttributeName;

import java.util.List;
import com.example.its.domainissue.IssueEntity;

@Controller
public class IssueController {

    // GET /issues
    @GetMapping("/issues")
    public String showList(Model model) {
        var issueList:List<IssueEntity> = List.of(
            new IssueEntity(1, "概要1", "説明1"),
            new IssueEntity(2, "概要2", "説明2"),
            new IssueEntity(3, "概要3", "説明3")

        );
        model.addAllAttribute(attributeName:"issueList",issueList);
        return "issues/list";
    }
}
```

## 6.VIEWを作成する
①src > main > resource > templeteに　issuesフォルダにlist.htmlファイルを作成する。

```issues/list.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題一覧 | 課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題一覧</h1>
    <table>
      <thead>
        <tr>
          <th>#</th>
          <th>概要</th>
        </tr>
      </thead>
      <tbody>
        <!-- Javaから渡された複数件のissueを１件ずつ取る -->
        <tr th:each="issue : ${issueList}">
          <th th:text="${issueList.id}">(id)</th>
          <td th:text="${issueList}.summry">(summary)</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>
```

## 7.ページ間を移動できる用にリンクを貼る

①templete/index.htmlファイルを編集する。

```templete/index.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題管理アプリケーション</h1>
    <ul>
      <li><a　href="./issues/list.html" th:href="@{/issues}">課題一覧</a></li>
    </ul>
  </body>
</html>
```
②templete/issues/list.htmlファイルを編集する。

```templete/issues/list.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題一覧 | 課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題一覧</h1>
    <a href="../index.html" th:href="@/">トップページ</a>
    <table>
      <thead>
        <tr>
          <th>#</th>
          <th>概要</th>
        </tr>
      </thead>
      <tbody>
        <!-- Javaから渡された複数件のissueを１件ずつ取る -->
        <tr th:each="issue : ${issueList}">
          <th th:text="${issueList.id}">(id)</th>
          <td th:text="${issueList}.summry">(summary)</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>
```
## 8.Lombokを使ってリファクタリングをする

※ Lombokとは、コンストラクターやゲッター、セッターのコードを自動生成してくれるライブラリーのこと
①下記のコードを加える。

```build.gradle
configurations {
  compileOnly {
    extendsFrom annotationProcessor
  }
}

//dependencies {
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
```

②@AllArgsConstructorと＠Dataをdomainissue/IssueEntityに記入する。

```domainissue/IssueEntity.java
package com.example.its.domainissue;

import lombok.AllArgsConstructor;
import lombok.Data;

@AllArgsConstructor
@Data
public class IssueEntity {
    private long id;
    private String summary;
    private String description;
}
```
## 9.サービス層を作ってビジネスロジックを分離する

①issueフォルダに、IssueServiceファイルを作成する。

```issue/IssueService.java
package com.example.its.web.issue;

import com.example.its.domainissue.IssueEntity;
import java.util.List;

public class IssueService {

    public List<IssueEntity> findAll() {
        return List.of(
                new IssueEntity(1, "概要1", "説明1"),
                new IssueEntity(2, "概要2", "説明2"),
                new IssueEntity(3, "概要3", "説明3"));
    }
}
```
②IssueController.javaファイルを編集する。

```issue/IssueController.java
package com.example.its.web.issue;

import com.example.its.web.issue.IssueService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IssueController {

    private final IssueService issueService = new IssueService();

    @GetMapping("/issues")
    public String showList(Model model) {
        model.addAttribute("issueList", issueService.findAll());
        return "issues/list";
    }
}
```

## 10.dependency injectionを使う

##### DIとは？
DI:Dependency Injection（依存性の注入）
依存性の注入：使いたいオブジェクトを直接newせず、外部から渡してもらうようにするというデザインパターン

『直接newせず外部からもらう』とは？


```sample.java
//DI無

@Controller
public class IssueController {                  //newしている
  private final IssueService IssueService = new IssueService();
}

//DI有
@Controller
public class IssueController {                  
  private final IssueService IssueService;
                      //コンストラクタを呼び出す側でnewしてもらう
public IssueController (IssueService IssueService){            
  this.IssueService = IssueService;
 }
}
```
①issue/IssueServiceに、@Serviceを加える。

```issue/IssueService.java
package com.example.its.web.issue;

import com.example.its.domainissue.IssueEntity;
import java.util.List;
import org.springframework.stereotype.Service;

//ビジネスロジック・アルゴリズム等が書かれた処理を提供する
@Service
public class IssueService {

    public List<IssueEntity> findAll() {
        return List.of(
                new IssueEntity(1, "概要1", "説明1"),
                new IssueEntity(2, "概要2", "説明2"),
                new IssueEntity(3, "概要3", "説明3"));
    }
}
```
②issue/IssueService.javaに、引数有のコンストラクターを用意する。

```issue/IssueSController.java
package com.example.its.web.issue;

import com.example.its.web.issue.IssueService;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
@RequiredArgsConstructor
public class IssueController {

    private final IssueService issueService;

    @GetMapping("/issues")
    public String showList(Model model) {
        model.addAttribute("issueList", issueService.findAll());
        return "issues/list";
    }
}
```

## 11.H2データベースとMyBatisのセットアップをする

①spring initializrに『H2 Database』を追加して、『MyBatis』を押し、下記のファイルにコードを加える。

```build.gradle
dependencies {
//
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2'
runtimeOnly 'com.h2database:h2'
}
```

② resources/application.propertiesを編集する。

```resources/application.properties
spring.thymeleaf.prefix=file:src/main/resources/templates/
spring.datasource.data-source-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:its;DB_CLOSE_ON_EXIT=TRUE;MODE=MySQL
spring.datasource.username=sa
spring.datasource.password=
```

③ src > main > resource に schema.sql と data.sqlでテーブル作成と初期データ投入する。

```resource/schema.sql
create table issues (
id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
summary VARCHAR(256) NOT NULl,
description VARCHAR(256) NOT NULl
);
```

```resource/data.sql
insert into issues (summary, description) values ('バグA', 'バグがあります');
insert into issues (summary, description) values ('機能要望B', 'Bに追加機能がほしいです');
insert into issues (summary, description) values ('画面Cが遅い', '早くしてほしいです');
```

## 12. データアクセス層を作ってデータベースにselect文を実行する

①domainissueフォルダを作成し、IssueReositoryファイルを作成する。

```domainissue/IssueReository.java
package com.example.its.domainissue;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface IssueRepository {
    @Select("Select * from issue")
    List<IssueEntity> findAll();
}
```
②IssueServiceファイルを編集する。

```domainissue/IssueService.java

package com.example.its.domainissue;

import lombok.RequiredArgsConstructor;
import com.example.its.domainissue.IssueEntity;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@RequiredArgsConstructor
public class IssueService {

    praivate final IssueRepository IssueRepository;

    public List<IssueEntity> findAll() {
        return IssueRepository.finalAll();
    }
}
```
## 13.課題作成のための入力フォームを作る

①HTML画面を作成する為、templete/issues/creationForm.htmlファイルを作成する。
```issues/creationForm.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題管理</h1>
    <form>
      <div>
        <label for="summaryInput">概要</label>
        <input type="text" id="summaryInput" />
      </div>
      <div>
        <label for="descriptionInput">説明</label>
        <textarea type="text" id="descriptionInput" row="10"></textarea>
      </div>
      <div>
        <button type="submit">作成</button>
      </div>
    </form>
  </body>
</html>
```
②issueControllerの中にハンドラーメソッドを作成する。

```issue/IssuesController.java
package com.example.its.web.issue;

import com.example.its.domainissue.IssueService;

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

@Controller
@RequestMapping("/issues")
@RequiredArgsConstructor
public class IssueController {

    private final IssueService issueService;

    @GetMapping
    public String showList(Model model) {
        model.addAttribute("issueList", issueService.findAll());
        return "issues/list";
    }

    @GetMapping("creationForm")
    public String showCreationForm() {
        return "issues/creationForm";
    }
}
```

③一覧画面に作成画面のリンクを表示する。

```templete/issues/list.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題一覧 | 課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題一覧</h1>
    <a href="../index.html" th:href="@/">トップページ</a>
    <a href="./creationtForm.html" th:href="@/issues/creationtForm">作成</a>
    <table>
      <thead>
        <tr>
          <th>#</th>
          <th>概要</th>
        </tr>
      </thead>
      <tbody>
        <!-- Javaから渡された複数件のissueを１件ずつ取る -->
        <tr th:each="issue : ${issueList}">
          <th th:text="${issueList.id}">(id)</th>
          <td th:text="${issueList}.summry">(summary)</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>
```

## 14. 課題作成のリクエストを受けるハンドラーメソッドを実装する
①イシューコントローラーの中にメソッドを作る。

```issue/IssueController.java
package com.example.its.web.issue;

import com.example.its.domainissue.IssueService;

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

@Controller
@RequestMapping("/issues")
@RequiredArgsConstructor
public class IssueController {

    private final IssueService issueService;

    @GetMapping
    public String showList(Model model) {
        model.addAttribute("issueList", issueService.findAll());
        return "issues/list";
    }

    @GetMapping("creationForm")
    public String showCreationForm() {
        return "issues/creationForm";
    }

    @PostMapping("creationForm")
    public String create(IssueForm form, Model model) {
        return showList(model);
    }
}
```

②issuesフォルダに、IssueFormファイルを作成する。

```issues/IssueForm.java
package com.example.its.web.issue;

import lombok.Data;

@Data
public class issueForm {
    private String summary;
    private String description;
}
```
③issues/creationForm.htmlを編集する。

```issues/creationForm.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題管理</h1>
    <form action="#" th:action="@{/issues}" th:method="post">
      <div>
        <label for="summaryInput">概要</label>
        <input type="text" id="summaryInput" />
      </div>
      <div>
        <label for="descriptionInput">説明</label>
        <textarea type="text" id="descriptionInput" row="10"></textarea>
      </div>
      <div>
        <button type="submit">作成</button>
      </div>
    </form>
  </body>
</html>
```
## 15.Viewからデータを受け取り、受け取ったデータをDBに保存する

①issues/creationForm.htmlを編集する。

```issues/creationForm.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題管理</h1>
    <form
      action="#"
      th:action="@{/issues}"
      th:method="post"
      th:object="${issueForm}"
    >
      <div>
        <label for="summaryInput">概要</label>
        <input type="text" id="summaryInput" th:field="*{summary}" />
      </div>
      <div>
        <label for="descriptionInput">説明</label>
        <textarea
          type="text"
          id="descriptionInput"
          row="10"
          th:field="*{description}"
        ></textarea>
      </div>
      <div>
        <button type="submit">作成</button>
      </div>
    </form>
  </body>
</html>
```

②issue/IssueController.javaを編集する。

```issue/IssueSController.java
public class IssueController {

    private final IssueService issueService;

    @GetMapping
    public String showList(Model model) {
        model.addAttribute("issueList", issueService.findAll());
        return "issues/list";
    }

    @GetMapping("creationForm")
    public String showCreationForm(@ModelAttribute issueForm form) {
        return "issues/creationForm";
    }

    @PostMapping
    public String create(issueForm form, Model model) {
        issueService.create(form.getSummary(), form.getDescription());
        return showList(model);
    }
}
```
③issue/IssueServiceファイルを編集する。

```issue/IssueService.java

package com.example.its.domainissue;

import lombok.RequiredArgsConstructor;
import com.example.its.domainissue.IssueEntity;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@RequiredArgsConstructor
public class IssueService {

    praivate final IssueRepository IssueRepository;

    public List<IssueEntity> findAll() {
        IssueRepository.insert(summary, description);
    }
}
````

④issue/IssueRepositoryファイルを編集する。

```issue/IssueRepository.java
package com.example.its.domainissue;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface IssueRepository {

    @Select("Select * from issues")
    List<IssueEntity> findAll();

    @Insert("insert into issue(summary, description) values(#{summary},#{description})")
    void insert(String summary, String description);
}
```
## 16. 登録処理にトランザクションを張る

※ トランザクションとは、複数の処理を1まとまりにして扱う考え方。
※ ロールバックとは、途中で処理が失敗したときに、それまでの変更なかったことにするという機能。

```issue/IssueService.java
package com.example.its.domainissue;

import lombok.RequiredArgsConstructor;
import com.example.its.domainissue.IssueEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class IssueService {

    praivate
    final IssueRepository IssueRepository;

    public List<IssueEntity> findAll() {
       return IssueRepository.findAll();
    }
    @Transactional
    public void create(String summary, String description) {
        IssueRepository.insert(summary, description);
    }
}
```
## 17. 2重サブミット対策をする

※2重サブミットとは、二重で実行されるとシステムとして困る操作を二重ですること
※2重サブフィットが起こる原因は、ブラウザーのリロード処理が直前のリクエストを再実行するから

①issue/IssueController.javaを編集する。

```issue/IssueController.java
public class IssueController {

    private final IssueService issueService;

    @GetMapping
    public String showList(Model model) {
        model.addAttribute("issueList", issueService.findAll());
        return "issues/list";
    }

    @GetMapping("creationForm")
    public String showCreationForm(@ModelAttribute issueForm form) {
        return "issues/creationForm";
    }

    @PostMapping
    public String create(issueForm form, Model model) {
        issueService.create(form.getSummary(), form.getDescription());
        return "redirect:/issues";
    }
}
```
## 18. バリデーションを実装する
①spring initializrに『Validation』を追加して、『EXPLORE』を押し、下記のファイルにコードを加える。

```build.gradle
dependencies {
//
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
```
②issues/IssueForm.javaを編集する。

```issues/IssueForm.java
package com.example.its.web.issue;

import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Data
public class issueForm {

    @NotBlankBlank
    @Size(max = 256)
    private String summary;

    @NotBlankBlank
    @Size(max = 256)
    private String description;
}
```
③issue/IssueController.javaを編集する。

```issue/IssueController.java
public class IssueController {

    private final IssueService issueService;

    @GetMapping
    public String showList(Model model) {
        model.addAttribute("issueList", issueService.findAll());
        return "issues/list";
    }

    @GetMapping("creationForm")
    public String showCreationForm(@ModelAttribute issueForm form) {
        return "issues/creationForm";
    }

    @PostMapping
    public String create(@Validated issueForm form, BindingResult bindingResult, Model model) {
        if(bindingResult.hasErrors()){
            return showCreationForm(form;)
        }
        issueService.create(form.getSummary(), form.getDescription());
        return "redirect:/issues";
    }
}
```

④issues/creationForm.htmlを編集する。

```issues/creationForm.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題管理</h1>
    <form
      action="#"
      th:action="@{/issues}"
      th:method="post"
      th:object="${issueForm}"
    >
      <div>
        <label for="summaryInput">概要</label>
        <input type="text" id="summaryInput" th:field="*{summary}" />
        <p th:if="${#fields.hasErrors('summary')}" th:errors="*{summary}">
          (error)
        </p>
      </div>
      <div>
        <label for="descriptionInput">説明</label>
        <textarea
          type="text"
          id="descriptionInput"
          row="10"
          th:field="*{description}"
        ></textarea>
        <p
          th:if="${#fields.hasErrors('description')}"
          th:errors="*{description}"
        >
          (error)
        </p>
      </div>
      <div>
        <button type="submit">作成</button>
      </div>
    </form>
  </body>
</html>

```
## 19. 課題詳細画面を実装
(1)View
①issues/detail.htmlを作成する。

```issues/detail.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題詳細 | 課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題詳細</h1>
    <div>
      <h2 th:text="${issue.summary}">(summary)</h2>
      <p th:text="${issue.description}">(description)</p>
    </div>
  </body>
</html>
```

(2)Controller
①issue/IssueController.javaを編集する。

```issue/IssueController.java
package com.example.its.web.issue;

import com.example.its.domainissue.IssueEntity;
import com.example.its.domainissue.IssueService;

import lombok.RequiredArgsConstructor;

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 org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/issues")
@RequiredArgsConstructor
public class IssueController {

    private final IssueService issueService;

    @GetMapping
    public String showList(Model model) {
        model.addAttribute("issueList", issueService.findAll());
        return "issues/list";
    }

    @GetMapping("creationForm")
    public String showCreationForm(@ModelAttribute issueForm form) {
        return "issues/creationForm";
    }

    @PostMapping
    public String create(@Validated issueForm form, BindingResult bindingResult, Model model) {
        if(bindingResult.hasErrors()){
            return showCreationForm(form;)
        }
        issueService.create(form.getSummary(), form.getDescription());
        return "redirect:/issues";
    }

    @GetMapping("/{issueId}")
    public String showDetail(@PathVariable("issueId") long issueId, Model model) {
        IssueEntity dummyEntity = new IssueEntity(id:1,summary:"概要", description:"説明"));
        model.addAttribute("issue", dummyEntity);
        return "issues/detail";
    }
}
```
(3)Serivce + Repository
①issue/IssueController.javaを編集する。

```issue/IssueController.java
package com.example.its.web.issue;

import com.example.its.domainissue.IssueEntity;
import com.example.its.domainissue.IssueService;

import lombok.RequiredArgsConstructor;

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 org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/issues")
@RequiredArgsConstructor
public class IssueController {

    private final IssueService issueService;

    @GetMapping
    public String showList(Model model) {
        model.addAttribute("issueList", issueService.findAll());
        return "issues/list";
    }

    @GetMapping("creationForm")
    public String showCreationForm(@ModelAttribute issueForm form) {
        return "issues/creationForm";
    }

    @PostMapping
    public String create(@Validated issueForm form, BindingResult bindingResult, Model model) {
        if(bindingResult.hasErrors()){
            return showCreationForm(form;)
        }
        issueService.create(form.getSummary(), form.getDescription());
        return "redirect:/issues";
    }

    @GetMapping("/{issueId}")
    public String showDetail(@PathVariable("issueId") long issueId, Model model) {
        model.addAttribute("issue", issueService.findById(issueId));
        return "issues/detail";
    }
}
```
②issue/IssueController.javaを編集する。

```issue/IssueService.java
package com.example.its.domainissue;

import lombok.RequiredArgsConstructor;
import com.example.its.domainissue.IssueEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class IssueService {

    praivate
    final IssueRepository IssueRepository;

    public List<IssueEntity> findAll() {

        return IssueRepository.findAll();
    }

    @Transactional
    public void create(String summary, String description) {
        IssueRepository.insert(summary, description);
    }

    public IssueEntity findById(long issueId){
        return IssueRepository.findById(issueId);
    }
}
```

③issue/IssueRepository.javaを編集する。

```issue/IssueRepository.java
package com.example.its.domainissue;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface IssueRepository {

    @Select("Select * from issues")
    List<IssueEntity> findAll();

    @Insert("insert into issue(summary, description) values(#{summary},#{description})")
    void insert(String summary, String description);

    @Select("select * from issues where id = #{issueId}")
    IssueEntity findById(long issueId);
}
```

(4)画面遷移のためのリンクを作る

①issue/IssueRepository.javaを編集する。

```issue/IssueRepository.java
package com.example.its.domainissue;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface IssueRepository {

    @Select("Select * from issues")
    List<IssueEntity> findAll();

    @Insert("insert into issue(summary, description) values(#{summary},#{description})")
    void insert(String summary, String description);

    @Select("select * from issues where id = #{issueId}")
    IssueEntity findById(long issueId);
}
```
②issues/list.htmlのth:textをaタグに変える。

```templete/issues/list.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題一覧 | 課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題一覧</h1>
    <a href="../index.html" th:href="@/">トップページ</a>
    <a href="./creationtForm.html" th:href="@/issues/creationtForm">作成</a>
    <table>
      <thead>
        <tr>
          <th>#</th>
          <th>概要</th>
        </tr>
      </thead>
      <tbody>
        <!-- Javaから渡された複数件のissueを１件ずつ取る -->
        <tr th:each="issue : ${issueList}">
          <th th:text="${issueList.id}">(id)</th>
          <td>
            <a
              href="./detail.html"
              th:href="@{/issueList/{issueId}(issueId=${issue.id})}"
              th:text="${issueList.summary}"
              >(summary)
            </a>
          </td>
        </tr>
      </tbody>
    </table>
  </body>
</html>
```
③issues/detail.htmlを編集する。

```issues/detail.html
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>課題詳細 | 課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題詳細</h1>
    <a href="./list.html" th:href="@{/issues}">一覧に戻る</a>
    <div>
      <h2 th:text="${issue.summary}">(summary)</h2>
      <p th:text="${issue.description}">(description)</p>
    </div>
  </body>
</html>
```

## 20. Thymleaf のすべてのページでフラグメントを使ってリファクタリングする
①templates/fragments/layout.htmlを作成する。

```templates/fragments/layout.html
<!DOCTYPE >
<html
  lang="en"
  xmlns:th="http://www.thymeleaf.org"
  th:fragment="layout(title,content)"
>
  <head>
    <meta charset="UTF-8" />
    <title th:replace="${title}">(default title)</title>
  </head>
  <body>
    <div th:insert="${content}">(default title)</div>
  </body>
</html>
```
②templates/issues/index.htmlを編集する。

```templates/issues/index.html
<!DOCTYPE html>
<html
  xmlns:th="http://www.thymeleaf.org"
  th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
  <head>
    <meta charset="UTF-8" />
    <title>トップページ | 課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題管理アプリケーション</h1>
    <ul>
      <li><a href="./issues/list.html" th:href="@{/issues}">課題一覧</a></li>
    </ul>
  </body>
</html>
```
③templates/issues/list.htmlをリファクタリングする。

```templates/issues/list.html
<!DOCTYPE html>
<html
  xmlns:th="http://www.thymeleaf.org"
  th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
  <head>
    <title>課題一覧 | 課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題一覧</h1>
    <a href="../index.html" th:href="@/">トップページ</a>
    <a href="./creationtForm.html" th:href="@/issues/creationtForm">作成</a>
    <table>
      <thead>
        <tr>
          <th>#</th>
          <th>概要</th>
        </tr>
      </thead>
      <tbody>
        <!-- Javaから渡された複数件のissueを１件ずつ取る -->
        <tr th:each="issue : ${issueList}">
          <th th:text="${issueList.id}">(id)</th>
          <td>
            <a
              href="./detail.html"
              th:href="@{/issueList/{issueId}(issueId=${issue.id})}"
              th:text="${issueList.summary}"
              >(summary)
            </a>
          </td>
        </tr>
      </tbody>
    </table>
  </body>
</html>
```

④templates/issues/detail.htmlをリファクタリングする。

```templates/issues/detail.html
<!DOCTYPE html>
<html
  xmlns:th="http://www.thymeleaf.org"
  th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
  <head>
    <title>課題詳細 | 課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題詳細</h1>
    <a href="./list.html" th:href="@{/issues}">一覧に戻る</a>
    <div>
      <h2 th:text="${issue.summary}">(summary)</h2>
      <p th:text="${issue.description}">(description)</p>
    </div>
  </body>
</html>
```
④templates/issues/creationForm.htmlをリファクタリングする。

```templates/issues/creationForm.html
<!DOCTYPE html>
<html
  xmlns:th="http://www.thymeleaf.org"
  th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
  <head>
    <title>課題管理アプリケーション</title>
  </head>
  <body>
    <h1>課題管理</h1>
    <form
      action="#"
      th:action="@{/issues}"
      th:method="post"
      th:object="${issueForm}"
    >
      <div>
        <label for="summaryInput">概要</label>
        <input type="text" id="summaryInput" th:field="*{summary}" />
        <p th:if="${#fields.hasErrors('summary')}" th:errors="*{summary}">
          (error)
        </p>
      </div>
      <div>
        <label for="descriptionInput">説明</label>
        <textarea
          type="text"
          id="descriptionInput"
          row="10"
          th:field="*{description}"
        ></textarea>
        <p
          th:if="${#fields.hasErrors('description')}"
          th:errors="*{description}"
        >
          (error)
        </p>
      </div>
      <div>
        <button type="submit">作成</button>
      </div>
    </form>
  </body>
</html>

```
## 20. Boostrapを使用する
(1) BoostrapをCDNから読み込む。
公式サイト : [Boostrap ](https://getbootstrap.com/)

```templates/fragments/layout.html
<!DOCTYPE >
<html
  lang="en"
  xmlns:th="http://www.thymeleaf.org"
  th:fragment="layout(title,content)"
>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title th:replace="${title}">(default title)</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <div th:insert="${content}"></div>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
      crossorigin="anonymous"
    ></script>
  </body>
</html>
```
(2) 余白を調整する:Container
①templates/fragments/layout.htmlに　divタグに　class="container"を記載する
```templates/fragments/layout.html
<!DOCTYPE >
<html
  lang="en"
  xmlns:th="http://www.thymeleaf.org"
  th:fragment="layout(title,content)"
>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title th:replace="${title}">(default title)</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <div class="container" th:insert="${content}"></div>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
      crossorigin="anonymous"
    ></script>
  </body>
</html>
```
(3)余白を調整する:Spacing
①htmlファイルに　h1要素にmt-3というclassを付ける。

（4）テーブルのスタイルを調整する

①templates/issues/list.htmlに、TABLE要素にCLASSでTABLEと書く。

(5)フォームのスタイルを調整する

①templates/issues/creationForm.htmlを概要のラベルにCLASSでフォームラベルを指定する。
②input要素にはCLASSのフォームコントロールを指定する。
③作成ボタンにボタンクラスとボタンプライマリークラスを付ける。

```templates/issues/creationForm.html
<!DOCTYPE html>
<html
  xmlns:th="http://www.thymeleaf.org"
  th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
  <head>
    <title>課題管理アプリケーション</title>
  </head>
  <body>
    <h1 class="mt-3">課題管理</h1>
    <form
      action="#"
      th:action="@{/issues}"
      th:method="post"
      th:object="${issueForm}"
    >
      <div class="mt-3">
        <label for="summaryInput" class="form-label">概要</label>
        <input
          type="text"
          id="summaryInput"
          th:field="*{summary}"
          class="form-control"
        />
        <p th:if="${#fields.hasErrors('summary')}" th:errors="*{summary}">
          (error)
        </p>
      </div>
      <div class="mt-3">
        <label for="descriptionInput" class="form-label">説明</label>
        <textarea
          type="text"
          id="descriptionInput"
          row="10"
          th:field="*{description}"
          class="form-control"
        ></textarea>
        <p
          th:if="${#fields.hasErrors('description')}"
          th:errors="*{description}"
        >
          (error)
        </p>
      </div>
      <div class="mt-3">
        <button type="submit" class="btn btn-primary">作成</button>
        <a href="./list.html" th:href="@{/issues}" class="btn btn-secondary"
          >キャンセル</a
        >
      </div>
    </form>
  </body>
</html>
```
(6)リデーションのスタイルを調整する
①バリデーションエラー時のみインバリとCLASSをCLASS属性に追加する。

```templates/issues/creationForm.html
<!DOCTYPE html>
<html
  xmlns:th="http://www.thymeleaf.org"
  th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
  <head>
    <title>課題管理アプリケーション</title>
  </head>
  <body>
    <h1 class="mt-3">課題管理</h1>
    <form
      action="#"
      th:action="@{/issues}"
      th:method="post"
      th:object="${issueForm}"
    >
      <div class="mt-3">
        <label for="summaryInput" class="form-label">概要</label>
        <input
          type="text"
          id="summaryInput"
          th:field="*{summary}"
          class="form-control"
          th:classappend="${#fields.hasErrors('summary')} ? is-invalid"
        />
        <p
          class="invalid-feedback"
          th:if="${#fields.hasErrors('summary')}"
          th:errors="*{summary}"
        >
          (error)
        </p>
      </div>
      <div class="mt-3">
        <label for="descriptionInput" class="form-label">説明</label>
        <textarea
          type="text"
          id="descriptionInput"
          row="10"
          th:field="*{description}"
          class="form-control"
          is-invalid
        ></textarea>
        <p
          class="invalid-feedback"
          th:if="${#fields.hasErrors('description')}"
          th:errors="*{description}"
        >
          (error)
        </p>
      </div>
      <div class="mt-3">
        <button type="submit" class="btn btn-primary">作成</button>
        <a href="./list.html" th:href="@{/issues}" class="btn btn-secondary"
          >キャンセル</a
        >
      </div>
    </form>
  </body>
</html>

```

## 参考サイト
[IntelliJ ではじめる Spring Boot：課題管理アプリを作って学ぶWebアプリケーション開発の基礎](https://www.udemy.com/course/intellij-spring-boot/learn/lecture/29236438#overview)
[【5分でわかる】Springでよく使うアノテーション40選！ 基本からバリデーションまでまるっとご紹介](https://camp.trainocate.co.jp/magazine/spring-annotation/)