内容
- 前回の続き
-
こちらの記事のKotlin版です
- Kotlin初心者なのでもっと良い書き方あれば是非コメントで教えて下さい
- SpringBootで簡単なCRUDアプリを作る
- 今回は入力域にバリデーションをかける
- 名前と年齢を必須項目にする
- 年齢は0~150の範囲内にする
- チーム名は20文字までにする
手順
- entityにバリデーションの設定を追加する
- controllerにエラー制御を追加する
- 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チェック対象となる- アノテーションを横並びにしているが
@Valid
はplayer
にかかっている
- アノテーションを横並びにしているが
@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を特定できない
動作確認
- ここまで修正した状態で、http://localhost:8080/players にアクセスすると以下のような動作になっている
次回
- 次はこちら