Kotlin + SpringBoot + JPA + Thymeleafで簡単なCRUDを作る②～画面と機能作成まで～

## 内容

- [前回](https://qiita.com/ozaki25/items/ec47df18ad06edd0934e)の続き
- [こちらの記事](https://qiita.com/ozaki25/items/3b348874b6db5ab4f04f)のKotlin版です
    - Kotlin初心者なのでもっと良い書き方あれば是非コメントで教えて下さい
- SpringBootで簡単なCRUDアプリを作る
- 今回は以下の4画面を一通り作成して処理を通すところまで
    - 一覧画面
    - 新規作成画面
    - 編集画面
    - 参照画面

## 開発する機能

1. 選手の情報を保存するplayerテーブルを作る
1. JPAを使ってplayerテーブルの操作をできるようにする
1. playerに関するエンドポイントの作成
1. 各画面のテンプレート作成

### 1. 選手の情報を保存するplayerテーブルを作る

- SpringBootとJPAを使っていればEntityクラスを作ると自動的にテーブルを生成してくれるのでSQLをたたく必要はない
- playerの情報を保存する`Player.kt`を`src/main/kotlin/com/example/baseball/domain`に作成する

```kotlin:src/main/kotlin/com/example/baseball/domain/Player.kt
package com.example.baseball.domain

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id

@Entity // ①
data class Player(
        @Id // ②
        @GeneratedValue // ③
        val id: Long? = null, // ④
        val name: String? = null, // ④
        val age: Int? = null, // ④
        val team: String? = null, // ④
        val position: String? = null // ④
)
```

- ①:`@Entity`をつけることでDBのテーブルと紐づく
- ②:`@Id`を付けた変数がテーブルのプライマーキーになる
- ③:`@GeneratedValue`をつけると連番が自動で振られるようになる
- ④:それぞれnullが入りうるので型に`?`をつけておく

### 2. JPAを使ってplayerテーブルの操作をできるようにする

- テーブルへアクセスするための基本的な処理はJPAが用意してくれているのでSQLを書かなくてよい
- JPAのJpaRepositoryを継承したinterfaceを作成することで利用できる
- findAllやsave等用意されている典型的な操作以外のことをしたくなったら、repositoryに追記していくことになる
- 　`src/main/kotlin/com/example/baseball/repository`に`PlayerRepository.kt`作成する

```kotlin:src/main/kotlin/com/example/baseball/repository/PlayerRepository.kt
package com.example.baseball.repository

import com.example.baseball.domain.Player
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface PlayerRepository : JpaRepository<Player, Long> {

}
```

- repositoryの処理を呼び出すserviceを作成する
- serviceにはビジネスロジックを書いて、repositoryはDBアクセスのみという棲み分け
- DBにアクセスするような処理を行いたい時はserviceのメソッドを呼ぶようにする
- 現段階ではビジネスロジックはないのでrepositoryのメソッドを呼ぶ処理だけ書いておく
- `src/main/kotlin/com/example/baseball/service`に`PlayerService.kt`作成する

```kotlin:src/main/kotlin/com/example/baseball/service/PlayerService.kt
package com.example.baseball.service

import com.example.baseball.repository.PlayerRepository
import org.springframework.stereotype.Service
import com.example.baseball.domain.Player

@Service
// ①
class PlayerService(private val playerRepository: PlayerRepository) {
    fun findAll() = playerRepository.findAll()

    fun findOne(id: Long) = playerRepository.findById(id).orElse(null)

    fun save(player: Player) = playerRepository.save(player)

    fun delete(id: Long) = playerRepository.deleteById(id)
}
```

- ①:コンストラクタにrepositoryを宣言しておくことでBeanをインジェクトしてくれる

### 3. playerに関するエンドポイントの作成

- ユーザがアクセスするURLに紐づくメソッドを作成する
- 作成するエンドポイントは以下の6種類

|HTTPメソッド|URL|Controllerのメソッド|概要|
|---|---|---|---|
|GET|/players|index()|一覧画面の表示|
|POST|/players|create()|データの保存|
|GET|/players/new|newPlayer()|新規作成画面の表示|
|GET|/players/1/edit|edit()|編集画面の表示|
|GET|/players/1|show()|参照画面の表示|
|PUT|/players/1|update()|データの更新|
|DELETE|/players/1|destroy()|データの削除|

- `src/main/kotlin/com/example/baseball/controller`に`PlayerController.kt`作成する

```kotlin:src/main/kotlin/com/example/baseball/controller/PlayerController.kt
package com.example.baseball.controller

import com.example.baseball.domain.Player
import com.example.baseball.service.PlayerService
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.*

@Controller
@RequestMapping("/players") // ①
class PlayerController(private val playerService: PlayerService) {
    @GetMapping
    // ②
    fun index(model: Model): String {
        // ③
        model.addAttribute("players", playerService.findAll())
        // ④
        return "players/index"
    }

    @GetMapping("new")
    fun newPlayer(): String {
        return "players/new"
    }

    @GetMapping("{id}/edit")
    // ⑤
    fun edit(@PathVariable id: Long, model: Model): String {
        model.addAttribute("player", playerService.findOne(id));
        return "players/edit"
    }

    @GetMapping("{id}")
    fun show(@PathVariable id: Long, model: Model): String {
        model.addAttribute("player", playerService.findOne(id));
        return "players/show"
    }

    @PostMapping
    // ⑥
    fun create(@ModelAttribute player: Player): String {
        playerService.save(player)
        // ⑦
        return "redirect:/players"
    }

    @PutMapping("{id}")
    fun update(@PathVariable id: Long, @ModelAttribute player: Player): String {
        playerService.save(player.copy(id = id))
        return "redirect:/players"
    }

    @DeleteMapping("{id}")
    fun destroy(@PathVariable id: Long): String {
        playerService.delete(id)
        return "redirect:/players"
    }
}
```

- ①:クラスに対して`@RequestMapping`を付けておくと、クラス内のメソッド全てに適用される
    - つまりこのクラスのメソッドは全て、`http://localhost:8080/players`から始まるURLにマッピングされている
- ②:メソッドの引数に`Model`型の値を設定するとModelのインスタンスが自動的に渡される
- ③:②で受け取ったmodelに値を詰めることで、テンプレートに値を渡すことができる
    - ここでは`players`というキー名でplayerのListを設定している
- ④:returnしている文字列を元に、`src/main/resources/templates/`配下からファイルを見つけてユーザに返している
    - ここでは`src/main/resources/templates/players/index.html`が返されている
- ⑤:メソッドの引数に`@PathVariable`を設定するとURL上の値を取得することができる
    - ここでは、`http://localhost/players/1`にアクセスされると`id`には1が入る
- ⑥:メソッドの引数に`@ModelAttribute`をつけると送信されたリクエストのbodyの情報を取得できる
- ⑦:"redirect:/players"とすると`/players`にリダイレクトされる
    - createメソッドの処理が終わった後に`http://localhost:8080/players`に勝手にアクセスされる感じ


### 4. 各画面のテンプレート作成

- 今回作成するのはplayerに関するテンプレートなので`src/main/resources/templates/players`以下に作成していく
- 作成するのは以下の4画面分のファイル
    - index.html(一覧画面)
    - new.html(新規作成画面)
    - edit.html(編集画面)
    - show.html(参照画面)

#### 静的ファイルの配置

- テンプレートを作る前にcssとjavascriptのファイルを準備しておく
- 今回は[Bootstrap](http://getbootstrap.com/)を使用する
    - 最新版は4系だけど今回は3系を使う
- [ここ](https://github.com/twbs/bootstrap/releases/download/v3.3.7/bootstrap-3.3.7-dist.zip)からダウンロードして、以下のファイルを配置する
    - cssフォルダ内の`bootstrap.css`を、`src/main/resources/static/css`フォルダにコピー
    - jsフォルダ内の`bootstrap.js`を`src/main/resources/static/js`フォルダにコピー
- Bootstrapが依存するjQueryをダウンロードして配置する
- jQueryは[ここ](https://raw.githubusercontent.com/jquery/jquery/1.12.4/dist/jquery.js)からダウンロード
    - ソースが直接出てしまう場合は右クリックで名前をつけて保存する
    - `src/main/resources/static/js`フォルダに`jquery.js`という名前で配置

<img width="268" alt="スクリーンショット 2017-10-25 11.08.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/175213/95a0e038-9012-74c0-e28e-33f1908a7937.png">


#### index.html

- playerの一覧をテーブルで表示する
- `src/main/resources/templates/players`に`index.html`を作成する

<img width="611" alt="index.png" src="https://qiita-image-store.s3.amazonaws.com/0/175213/80aedb52-f467-2562-087d-abfb13128dcd.png">

```html:src/main/resources/templates/players/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8" />
    <title>Listing Players - baseball</title>
    <!-- ① -->
    <link rel="stylesheet" href="/css/bootstrap.css" />
    <script src="/js/jquery.js"></script>
    <script src="/js/bootstrap.js"></script>
  </head>
  <body>
    <div class="container">
      <h1>Listing Players</h1>
      <table class="table">
        <thead>
          <tr>
            <th>ID</th>
            <th>名前</th>
            <th>年齢</th>
            <th>チーム名</th>
            <th>守備位置</th>
            <th></th>
            <th></th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          <!-- ② -->
          <tr th:each="player:${players}" th:object="${player}">
            <!-- ③ -->
            <td th:text="*{id}"></td>
            <td th:text="*{name}"></td>
            <td th:text="*{age}"></td>
            <td th:text="*{team}"></td>
            <td th:text="*{position}"></td>
            <!-- ④ -->
            <td><a class="btn btn-default btn-xs" th:href="@{/players/{id}(id=*{id})}">参照</a></td>
            <td><a class="btn btn-default btn-xs" th:href="@{/players/{id}/edit(id=*{id})}">編集</a></td>
            <td>
              <!-- ⑤ -->
              <form th:action="@{/players/{id}(id=*{id})}" th:method="delete">
                <input class="btn btn-default btn-xs" type="submit" value="削除" />
              </form>
            </td>
          </tr>
        </tbody>
      </table>
      <a class="btn btn-default" href="/players/new">新規作成</a>
    </div>
  </body>
</html>
```

- ①:cssとjavascriptのファイルを読み込む
- ②:Controllerから渡された`players`に対してループ処理を行っている
    - `th:each="player:${players}"`のようにすることでループ処理を記述できる
    - ここの例ではListであるplayersの数だけループを回し、List内の各要素をplayerという変数名で扱う
- ③:`th:object="${player}"`とすることで、このタグより子供の要素で、`${player.xxx}`を`*{xxx}`と省略して書けるようになる
- ④:`th:href="xxx"`とすることでhref属性に設定する値を動的にすることができる
    - `/players/{id}`の部分がhref属性に設定される値。`{id}`の部分は変数を埋め込むことができて、後続の`(id=*{id})`で値を設定している
- ⑤:削除の処理はHTTPメソッドをDELETEにしてアクセスしたいので、formタグにして`th:method="delete"`を付けている

#### new.html

- playerの新規作成画面
- `src/main/resources/templates/players`に`new.html`を作成する

<img width="605" alt="new.png" src="https://qiita-image-store.s3.amazonaws.com/0/175213/c0c0ddff-3dae-c2d2-e53e-70c18f647a70.png">

```html:src/main/resources/templates/players/new.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8" />
    <title>New Player - baseball</title>
    <link rel="stylesheet" href="/css/bootstrap.css" />
    <script src="/js/jquery.js"></script>
    <script src="/js/bootstrap.js"></script>
  </head>
  <body>
    <div class="container">
      <h1>New Player</h1>
      <form th:action="@{/players}" th:method="post">
        <div class="form-group">
          <label class="control-label">名前</label>
          <input class="form-control" type="text" name="name" />
        </div>
        <div class="form-group">
          <label class="control-label">年齢</label>
          <input class="form-control" type="number" name="age" />
        </div>
        <div class="form-group">
          <label class="control-label">チーム名</label>
          <input class="form-control" type="text" name="team" />
        </div>
        <div class="form-group">
          <label class="control-label">守備位置</label>
          <input class="form-control" type="text" name="position" />
        </div>
        <button class="btn btn-default" type="submit">作成</button>
      </form>
      <div class="pull-right">
        <a class="btn btn-link" href="/players">一覧画面へ</a>
      </div>
    </div>
  </body>
</html>
```

#### edit.html

- URLに含まれる値とidが一致するplayerの編集をする画面
- `src/main/resources/templates/players`に`edit.html`を作成する

<img width="608" alt="edit.png" src="https://qiita-image-store.s3.amazonaws.com/0/175213/8c4122c3-1267-5de1-775e-9bdc23dde85d.png">

```html:src/main/resources/templates/players/edit.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8" />
    <title>Editing Player - baseball</title>
    <link rel="stylesheet" href="/css/bootstrap.css" />
    <script src="/js/jquery.js"></script>
    <script src="/js/bootstrap.js"></script>
  </head>
  <body>
    <div class="container">
      <h1>Editing Player</h1>
      <form th:action="@{/players/{id}(id=*{id})}" th:method="put" th:object="${player}">
        <div class="form-group">
          <label class="control-label">名前</label>
          <!-- ① -->
          <input class="form-control" type="text" th:field="*{name}" />
        </div>
        <div class="form-group">
          <label class="control-label">年齢</label>
          <input class="form-control" type="number" th:field="*{age}" />
        </div>
        <div class="form-group">
          <label class="control-label">チーム名</label>
          <input class="form-control" type="text" th:field="*{team}" />
        </div>
        <div class="form-group">
          <label class="control-label">守備位置</label>
          <input class="form-control" type="text" th:field="*{position}" />
        </div>
        <button class="btn btn-default" type="submit">更新</button>
      </form>
      <div class="pull-right">
        <a class="btn btn-link" href="/players">一覧画面へ</a>
      </div>
    </div>
  </body>
</html>
```

- ①:`th:field`に設定した値は、name属性id属性に変数名、value属性に値が設定される
    - `th:field="*{age}"`として`*{age}`の値が20の場合、`name="age" id="age" value="20"`が自動的に設定されている

#### show.html

- URLに含まれる値とidが一致するplayerの情報を参照する画面
- `src/main/resources/templates/players`に`show.html`を作成する

<img width="597" alt="show.png" src="https://qiita-image-store.s3.amazonaws.com/0/175213/f4226e0d-b230-bd81-b9c7-84222795ea67.png">

```html:src/main/resources/templates/players/show.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8" />
    <title>Show Player - baseball</title>
    <link rel="stylesheet" href="/css/bootstrap.css" />
    <script src="/js/jquery.js"></script>
    <script src="/js/bootstrap.js"></script>
  </head>
  <body>
    <div class="container">
      <h1>Show Player</h1>
      <div th:object="${player}">
        <div>
          <label>ID</label>
          <p th:text="*{id}"></p>
        </div>
        <div>
          <label>名前</label>
          <p th:text="*{name}"></p>
        </div>
        <div>
          <label>年齢</label>
          <p th:text="*{age}"></p>
        </div>
        <div>
          <label>チーム名</label>
          <p th:text="*{team}"></p>
        </div>
        <div>
          <label>守備位置</label>
          <p th:text="*{position}"></p>
        </div>
      </div>
      <div>
        <a class="btn btn-default" th:href="@{/players/{id}/edit(id=*{id})}">編集</a>
      </div>
      <div class="pull-right">
        <a class="btn btn-link" href="/players">一覧画面へ</a>
      </div>
    </div>
  </body>
</html>
```

## 動作確認

- ここまでできて、http://localhost:8080/players にアクセスすると以下のような機能ができている

![demo.gif](https://qiita-image-store.s3.amazonaws.com/0/175213/3d736ae2-0bbc-cf42-4934-da38260a15f1.gif)

## 次回

- 続きは[こちら](https://qiita.com/ozaki25/items/c4ea7af5a93909826478)