LoginSignup
3
4

More than 5 years have passed since last update.

Kotlin + SpringBoot + JPA + Thymeleafで簡単なCRUDを作る③~Validationの追加~

Last updated at Posted at 2018-12-30

内容

  • 前回の続き
  • こちらの記事のKotlin版です
    • Kotlin初心者なのでもっと良い書き方あれば是非コメントで教えて下さい
  • SpringBootで簡単なCRUDアプリを作る
  • 今回は入力域にバリデーションをかける
    • 名前と年齢を必須項目にする
    • 年齢は0~150の範囲内にする
    • チーム名は20文字までにする

スクリーンショット 2017-10-25 22.49.22.png

手順

  1. entityにバリデーションの設定を追加する
  2. controllerにエラー制御を追加する
  3. templateにエラーメッセージを追加する

1. entityにバリデーションの設定を追加する

  • validationの設定はentityクラスの変数にアノテーションをつける
  • Player.ktを編集する
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
import javax.validation.constraints.*

@Entity
data class Player(
        @Id
        @GeneratedValue
        val id: Long? = null,
        @get:NotEmpty // ①
        val name: String? = null,
        @get:NotNull // ②
        @get:Min(value = 0) // ③
        @get:Max(value = 150)
        val age: Int? = null,
        @get:Size(max = 20) // ④
        val team: String? = null,
        val position: String = null
)
  • ①:@get:NotEmptyをつけるとnullと空文字の入力を許容しなくなる
  • ②:@get:NotNullをつけるとnullの入力を許容しなくなる
  • ③:最小値は@get:Min最大値は@get:Maxで設定する
  • ④:文字数の制限は@get:Sizeをつける

2. controllerにエラー制御を追加する

  • エラーがあった場合にもとの画面に戻す処理を追加する
  • PlayerController.ktを修正する
src/main/kotlin/com/example/baseball/controller/PlayerController.kt
    @GetMapping("new")
    fun newPlayer(model: Model): String {
        // ①
        model.addAttribute("player", Player())
        return "players/new"
    }

    @PostMapping
    // ②
    fun create(@Valid @ModelAttribute player: Player, bindingResult: BindingResult): String {
        // ③
        if (bindingResult.hasErrors()) return "players/new";
        playerService.save(player)
        return "redirect:/players"
    }

    @PutMapping("{id}")
    // ②
    fun update(@PathVariable id: Long, @Valid @ModelAttribute player: Player, bindingResult: BindingResult): String {
        // ③
        if (bindingResult.hasErrors()) return "players/edit";
        playerService.save(player.copy(id = id))
        return "redirect:/players"
    }
  • ①:新規作成画面に対してPlayerインスタンスを渡すようにする
    • これがないと入力エラー時に入力していた内容を保持することができない
  • ②:player@Validをつけることでvalidationチェック対象となる
    • アノテーションを横並びにしているが@Validplayerにかかっている
@Valid @ModelAttribute player: Player
// ↑と↓は同じ
@Valid
@ModelAttribute
player: Player
  • ②:エラーがあるとbindingResult: BindingResultの中にエラーの情報がセットされる
  • ③:エラーがあるとbindingResult.hasErrors()がtrueを返すのでその場合はもとの画面に返している

3. templateにエラーメッセージを追加する

new.html

  • エラーがあってcontrollerに突き返された場合にエラーメッセージを表示するようにする
src/main/resources/templates/players/new.html
      <!-- ① -->
      <form th:action="@{/players}" th:method="post" th:object="${player}">
        <!-- ② -->
        <div class="form-group" th:classappend="${#fields.hasErrors('name')}? has-error">
          <label class="control-label">名前</label>
          <input class="form-control" type="text" th:field="*{name}" />
          <!-- ③ -->
          <span class="text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
        </div>
        <div class="form-group" th:classappend="${#fields.hasErrors('age')}? has-error">
          <label class="control-label">年齢</label>
          <input class="form-control" type="number" th:field="*{age}" />
          <span class="text-danger" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></span>
        </div>
        <div class="form-group" th:classappend="${#fields.hasErrors('team')}? has-error">
          <label class="control-label">チーム名</label>
          <input class="form-control" type="text" th:field="*{team}" />
          <span class="text-danger" th:if="${#fields.hasErrors('team')}" th:errors="*{team}"></span>
        </div>
        <div class="form-group" th:classappend="${#fields.hasErrors('position')}? has-error">
          <label class="control-label">守備位置</label>
          <input class="form-control" type="text" th:field="*{position}" />
          <span class="text-danger" th:if="${#fields.hasErrors('position')}" th:errors="*{position}"></span>
        </div>
        <button class="btn btn-default" type="submit">作成</button>
      </form>
  • ①:controllerからplayerを受け取るようになったので、th:objectにセット
    • 各inputフィールドもth:fieldを使うように修正(ここの③参照)
    • なぜこの変更を入れたかというとエラーがあって突き返された時に、入力内容をvalueにセットし直したいから
  • ②:th:classappend="${#fields.hasErrors('name')}? has-error"の意味は、#fields.hasErrors('name')がtrueの時にclass属性にhas-errorを追加するよということ
    • #fieldsの中にエラーの情報が入っている
    • class属性にhas-errorをつけると、Bootstrapの定義によってラベルと枠が赤くなる
  • ③:エラーメッセージを表示するための領域
    • th:ifが設定された要素は、th:ifの値がtrueの時(ここで言うとエラーがあった時)のみ表示される
    • th:errors="*{name}"*{name}に対してセットされたエラーメッセージを全て表示させる
      • 今回の例では起きないが複数エラーがあったら改行して全て表示される

edit.html

  • new.htmlと同様の処理を追加
src/main/resources/templates/players/edit.html
      <form th:action="@{/players/{id}(id=*{id})}" th:method="put" th:object="${player}">
        <!-- ① -->
        <input type="hidden" th:field="*{id}">
        <div class="form-group" th:classappend="${#fields.hasErrors('name')}? has-error">
          <label class="control-label">名前</label>
          <input class="form-control" type="text" th:field="*{name}" />
          <span class="text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
        </div>
        <div class="form-group" th:classappend="${#fields.hasErrors('age')}? has-error">
          <label class="control-label">年齢</label>
          <input class="form-control" type="number" th:field="*{age}" />
          <span class="text-danger" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></span>
        </div>
        <div class="form-group" th:classappend="${#fields.hasErrors('team')}? has-error">
          <label class="control-label">チーム名</label>
          <input class="form-control" type="text" th:field="*{team}" />
          <span class="text-danger" th:if="${#fields.hasErrors('team')}" th:errors="*{team}"></span>
        </div>
        <div class="form-group" th:classappend="${#fields.hasErrors('position')}? has-error">
          <label class="control-label">守備位置</label>
          <input class="form-control" type="text" th:field="*{position}" />
          <span class="text-danger" th:if="${#fields.hasErrors('position')}" th:errors="*{position}"></span>
        </div>
        <button class="btn btn-default" type="submit">更新</button>
      </form>
  • ①:Kotlin版ではidはhiddenで明示的に送らないと送信されない
    • これがないとバリデーションエラーで編集画面に戻されたときに送信先URLに入れるIDを特定できない

動作確認

demo2.gif

次回

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4