【SpringBoot入門】フォームのバリデーションチェック


#目的

[Spring Quickstart Guide](https://spring.io/quickstart)を取り組み終えた方、SpringBootを学び始めた方、復習をしたい方に向けて、

公式ガイド[Validating Form Input](https://spring.io/guides/gs/validating-form-input/)を実際に取り組み学んだことを共有します。

完成形はこちらになります。

名前と年齢を入力するフォームがあり、
![スクリーンショット 2020-07-09 14.05.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/d0bef22f-dd42-df35-a5e6-04ee1d7b28a2.png)

不正な値が入力したままSubmitボタンが押されるとエラーメッセージを表示して、
![スクリーンショット 2020-07-09 14.05.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/24792711-980a-c493-9a79-d3e2a0be25ce.png)

有効な値が入力されている場合は、別の画面へと遷移出来るように実装していきます。
![スクリーンショット 2020-07-09 14.12.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/a8bc15dd-6017-7f48-d434-a1eaf76e9184.png)



開発環境、これまでのおさらいは以下になります。

```text:開発環境
OS： macOS Mojave バージョン10.14.6
テキストエディタ： Visual Studio Code（以下VSCode）
Java: 11.0.2
```


[QuickstartGuideのおさらいはこちらから](https://qiita.com/morioheisei/items/59384f015e9e3801c4a7)
[Building a RESTful Web Service編のおさらいはこちらから](https://qiita.com/morioheisei/items/03106a823063677fe2f7)
[Consuming a RESTful Web Service編のおさらいはこちらから](https://qiita.com/morioheisei/items/c0a7432ad0f7281730d5)
[Accessing Data with JPA編のおさらいはこちらから](https://qiita.com/morioheisei/items/3d93da3757f27a158466)
[Handling Form Submission編のおさらいはこちらから](https://qiita.com/morioheisei/items/96d71871dbd798bc5cac)


#1.SpringBoot projectを始めよう！

まずは、[spring initializr](https://start.spring.io/) にアクセスします。

1.ADD DEPENDENCIESボタンをクリックして、`Spring Web`と`Thymeleaf`を追加。
2.Artifact, Nameは、`validating-form-input`に変更。
3.`Javaを11`に変更。

そして`GENERATE`ボタンをクリックしてZipファイルをダウンロードします。

![スクリーンショット 2020-07-09 14.25.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/b74f3613-af40-e28e-0ab6-e019bd6cdfdf.png)

ダウンロードしたZipファイルを展開したら準備完了です。

#2.コードを追加しよう！

先ほどのフォルダをVSCodeで開きます。
拡張機能のJava Extension Packのインストールの推奨します。と言われるのでインストールしておきましょう。

![スクリーンショット 2020-06-30 10.08.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/80331ebc-b6e3-af7b-181a-3c8921288b00.png)

##PersonForm.javaを作成しよう！

`src/main/java/com/example/validatingforminput/` にPersonForm.javaファイルを作成します。

![スクリーンショット 2020-07-09 14.30.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/a62700fe-fd88-4f68-3a2c-911bb7f877a1.png)


公式を参考にコードを追加します。

```java:PersonForm.java
package com.example.validatingforminput;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class PersonForm {

  @NotNull
  @Size(min=2, max=30)
  private String name;

  @NotNull
  @Min(18)
  private Integer age;

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public String toString() {
    return "Person(Name: " + this.name + ", Age: " + this.age + ")";
  }
}
```

追加したコードを深掘りしていきます。

###①`@NotNull`と`@Size`

```java:PersonForm.java
@NotNull
@Size(min=2, max=30)
private String name;
```

String型のname変数を宣言時に2つバリデーションのアノテーションを付与しています。

`@NotNull`は、**nullを許可しません**。あくまでnullは許可しないだけで、空文字や空白は許可されるので注意してください。

`@Size`は、`()`の中で指定した**min以上**、**max以下**であるかを検証しています。
今回は、`@Size(min=2, max=30)`ですので、2文字以上30文字以下ではないとエラーになります。

###②`@Min`

```java:PersonForm.java
@NotNull
@Min(18)
private Integer age;
```

Integer型のage変数を宣言時に2つバリデーションのアノテーションを付与しています。（1つは上述の`@NotNull`）

`@Min`は、`()`の中に記述した値より小さいかどうか検証しています。
今回は、`@Min(18)`ですので、18未満はエラーになります。

###③ゲッター/セッター、toStringメソッド

```java:PersonForm.java
public String getName() {
  return this.name;
}

public void setName(String name) {
  this.name = name;
}

public Integer getAge() {
  return age;
}

public void setAge(Integer age) {
  this.age = age;
}

public String toString() {
  return "Person(Name: " + this.name + ", Age: " + this.age + ")";
}
```

変数name、ageの値を取得、変更するためのゲッター/セッターメソッドを定義しています。
また、name、ageを文字列として表示するためのtoStringメソッドを定義しています。（今回使用していない気がしますが、デバッグ用なのかな？）

##pom.xmlにvalidationの依存関係を追加しよう！

公式ガイドには記載がなかったのですが、Spring Boot 2.3以上の時は、
`PersonForm.java`の`import javax.validation.constraints.〇〇`がエラーになります。
([Spring Boot2.3 Relealse Notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#validation-starter-no-longer-included-in-web-starters))

以下をdependenciesに追加してください。

```xml:pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
```

![スクリーンショット_2020-07-09_15_38_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/65bfada7-b531-f6e2-5e4f-3750df9280c5.png)



##WebController.javaを作成しよう！

`src/main/java/com/example/validatingforminput/` にWebController.javaファイルを作成します。

![スクリーンショット 2020-07-09 15.41.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/d697f855-e7a1-de69-4f59-01ce7d9a20de.png)


公式を参考にコードを追加します。

```java:WebController.java
package com.example.validatingforminput;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Controller
public class WebController implements WebMvcConfigurer {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/results").setViewName("results");
  }

  @GetMapping("/")
  public String showForm(PersonForm personForm) {
    return "form";
  }

  @PostMapping("/")
  public String checkPersonInfo(@Valid PersonForm personForm, BindingResult bindingResult) {

    if (bindingResult.hasErrors()) {
      return "form";
    }

    return "redirect:/results";
  }
}
```

追加したコードを深掘りしていきます。

###①WebMvcConfigurerインターフェースとaddViewControllersメソッド

```java:WebController.java
@Controller
public class WebController implements WebMvcConfigurer {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/results").setViewName("results");
  }

 // 以下略
}
```

WebMvcConfigurerインターフェースを実装、addViewControllersメソッドをオーバーライドしています。

`http://localhost:8080/results`というURLの時、**results.html**というテンプレートを参照するように設定していると解釈しています。
URLとテンプレートがマッピングされるようにしているみたいです。results.htmlの実装は後ほど。


###②showFormメソッド

```java:WebController.java
@GetMapping("/")
public String showForm(PersonForm personForm) {
  return "form";
}
```

`@GetMapping`は、`http://localhost:8080/`でGETリクエストがあった時、showFormメソッドを呼ぶためのアノテーションです。
引数にPersonFormを受け取っています。メソッドの戻り値である、`form.html`のform属性でPersonFormを関連づける事ができます。
form.htmlの実装は後ほど。

###③checkPersonInfoメソッド

```java:WebController.java
@PostMapping("/")
public String checkPersonInfo(@Valid PersonForm personForm, BindingResult bindingResult) {

  if (bindingResult.hasErrors()) {
    return "form";
  }

  return "redirect:/results";
}
```

`@PostMapping`は、`http://localhost:8080/`でPOSTリクエストがあった時、checkPersonInfoメソッドを呼ぶためのアノテーションです。
1つ目の引数`@Valid PersonForm personForm`は入力されたデータの検証をしています。
2つ目の引数`BindingResult bindingResult`は、入力されたデータと検証結果（エラーがあるかどうか）を保持するためのアノテーションです。

`bindingResult.hasErrors()`が書かれているif文では、エラーがあるかどうかを確認しています。
もしエラーがある場合、form.htmlをエラーメッセージと入力されていた値と共に再描画します。

エラーが無ければ、`http://localhost:8080/results`にリダイレクトされます。

##form.htmlを作成しよう！

`src/main/resources/templates/` にform.htmlファイルを作成します。

![スクリーンショット 2020-07-10 10.18.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/c16172a8-857a-9da6-b613-7b6871b6c925.png)

公式を参考にコードを追加します。

```html:form.html
<html xmlns:th="http://www.thymeleaf.org">
  <body>
      <form action="#" th:action="@{/}" th:object="${personForm}" method="post">
        <table>
          <tr>
            <td>Name:</td>
            <td><input type="text" th:field="*{name}" /></td>
            <td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</td>
          </tr>
          <tr>
            <td>Age:</td>
            <td><input type="text" th:field="*{age}" /></td>
            <td th:if="${#fields.hasErrors('age')}" th:errors="*{age}">Age Error</td>
          </tr>
          <tr>
            <td><button type="submit">Submit</button></td>
          </tr>
        </table>
      </form>
  </body>
</html>
```

nameとageを入力するフォーム画面ですね。

追加したコードのthymeleafの記述について深掘りしていきます。

> thymeleafとは、springbootで扱う事が出来るテンプレートエンジンです。th:〇〇と記述します。
> 日本語で書かれた[thymeleafチュートリアル](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html#thymeleaf%E3%81%AE%E7%B4%B9%E4%BB%8B)もあります！


**th:action**

formタグのaction属性の内容を置換しています。記述の仕方は、`th:action="@{}"`です。
method="post"となっているので、Submitボタンが押された時にWebControllerのcheckPersonInfoメソッドが呼ばれます。

**th:object**

`th:object`でオブジェクトを指定しています。これにより、オブジェクト内の変数の参照の仕方がpersonForm.nameではなく、*{name}のような記述方法が可能になります。

**th:field**

th:objectで指定したオブジェクト内の変数を表示するために`th:field="*{変数名}"`と記述します。
今回は、PersonFormクラスの中にname、ageがあるので、`th:field="*{name}"`、`th:field="*{age}"`となります。
また、`th:field="*{変数名}"`の中に記述した変数名がinputのid属性とname属性になります。

**th:if**

`th:if=条件`と記述します。trueであった場合、そのタグ、子要素が表示されます。
今回の条件は、エラーがあった場合trueになります。

**th:errors**

`th:errors="*{変数名}"`と記述します。エラーがあった場合のエラーメッセージが表示されます。
th:ifとth:errorsでエラーの有無を確認して、エラーメッセージ表示する領域を確保している感じですね。


##results.htmlを作成しよう！

`src/main/resources/templates/` にresults.htmlファイルを作成します。

![スクリーンショット 2020-07-10 11.11.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/e3a330b7-d64e-33e8-a48e-c5877cfd8b02.png)


公式を参考にコードを追加します。

```html:results.html
<html>
  <body>
    Congratulations! You are old enough to sign up for this site.
  </body>
</html>
```

form画面で不正な値が入力されることが無ければこちらの画面に遷移します。

#3.実行してみよう！

アプリケーション実行の準備が出来たので確認しましょう。

ターミナルで以下のコマンドを入力してEnterしてください。

```terminal:ターミナル
$ ./mvnw spring-boot:run
```

そして、`http://localhost:8080/`にアクセスすると以下のフォーム画面が表示されるはずです。（form.htmlが表示される）

![スクリーンショット 2020-07-09 14.05.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/b2075fd2-cb6a-44d7-99e5-181ee31452ad.png)

**nameに1文字、ageに18未満の数値を入力して、Submitボタンを押すとエラーメッセージが表示されます。**

![スクリーンショット 2020-07-10 13.38.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/5bb4c39d-f2d7-cd5f-c967-060c4a2d6559.png)

![スクリーンショット 2020-07-10 13.38.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/259e3294-53d5-edfa-ffea-875c3dbdf985.png)

**ageに何も入力せずにSubmitボタンを押してもエラーメッセージが表示されます。**

![スクリーンショット 2020-07-10 13.41.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/4e8332f4-e23b-edd6-2da4-564dec4f9a04.png)

**nameに2文字以上、ageを18以上の数値を入力して、Submitボタンを押すとresult画面（result.html）が表示されます。**

![スクリーンショット 2020-07-10 13.45.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/d8e5134b-bdd8-11a6-07bc-1d9356a45cde.png)

![スクリーンショット 2020-07-10 13.45.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/17df7f90-e863-43fd-90c2-ef4ea2313da4.png)


お疲れ様でした！完成です！



#参考サイト

**[Spring Bootで入力値の検証](https://blog.okazuki.jp/entry/2015/07/05/115155)**
**[ビュー名を返すだけのControllerなら、Controllerは別にいらないらしいよ！](https://uchi-fashion.hatenablog.com/entry/2016/09/13/015015)**
**[検証とエラーメッセージ](https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html)**