1
2

Spring Bootでバリデーションを試してみた

Posted at

背景・目的

前回、下記でデータの登録・更新・削除などを試しました。今回はバリデーションを試してみます

まとめ

下記に特徴をまとめます

特徴 説明
バリデーションのパッケージ spring-boot-starter-validation
パッケージは二種類ある ・jakarta.validation
・jakarta.validation.constraints
標準バリデーション 以降のものがある
入力されているかのバリデーション @Null
@NotNull
@NotBlank
@NotEmpty
数値の範囲のバリデーション @Min
@Max
@DecimalMin
@DecimalMax
数値の桁数のバリデーション @Digits
日時のバリデーション @Future
@Past
オブジェクト数 @Size
正規表現 @Pattern
バリデーションのカスタマイズ エラーメッセージをカスタマイズするには、下記の方法がある
・アノテーション内のmessageのバリューに記載する
・resource/ValidationMessages.properties内に記載する

バリデーション自体を実装する

概要

Spring Boot 3 プログラミング入門を基に整理します

  • SpringBootのバリデーションには、spring-boot-starter-validation というパッケージが用意されている

validationのアノテーション

Springではバリデーションは、2種類用意されている。

  • jakarta.validation
  • jakarta.validation.constraints
アノテーション 説明 備考
@Null Nullである
@NotNull Nullではない 何も入力しない場合、空文字になるのですり抜けるので注意が必要
@NotBlank 空白ではない 入力フォームはこちらがよい
@NotEmpty 空ではない NotBlankとの違いは、半角スペース等でも入力したとみなす
@Min 最小値
@Max 最大値
@DecimalMin BigDecimal、int、long等値全般で使える最小値
@DecimalMax BigDecimal、int、long等値全般で使える最大値
@Digits 整数と小数部分の桁数制限を行う 使い方。@Digits(integer=5,fraction=10)
@Future 現在より先の日時のみ受付可能
@Past 現在よりも過去の日時のみ受付可能
@Size 文字列、配列、コレクションなど、いくつもの値をまとめて保管するオブジェクトで使用する @Size(min=1,max=10)
@Pattern String項目で使う。正規表現のパターンを指定して入力チェックを行う

エラーメッセージ

  • バリデーションで表示されるエラーメッセージは、あらかじめバリデーションパッケージに用意されている
  • エラーメッセージをカスタマイズする場合は、messageを使用して表示すれば良い

エラーメッセージをプロパティファイルで定義

  • エンティティクラスのアノテーション部分にメッセージを直接記述するのは、メンテナンス性が良くない
  • メッセージをプロパティにまとめて扱うのが良い
  • resourcesフォルダに、ValidationMessages.propertiesに定義する

オリジナルのバリデーター

  • アノテーションで実装されたバリデーションは、バリデータと呼ばれるデータチェック用のクラスにより行われる
  • 不足する場合は、自作も可能
  • 自作には2つのクラスが必要
    • アノテーションクラス
    • バリデーションを行うバリデータクラス
  • アノテーションクラス
    • @interface アノテーション名とする
    • java.lang.annotation.Annotationインタフェイスのサブクラスとして認識される
  • バリデータクラス
    • jakarta.validation.ConstraintValidatorインタフェイスを実装する

実践

Spring Boot 3 プログラミング入門を参考に試してみます。

前提

Spring Bootでデータを保存する-その2(更新と削除)で実装した内容を基にしています

pom.xml

  1. 下記のdependencyを追加します
    <dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-validation</artifactId>
    		<version>3.3.1</version>
    </dependency>
    

バリデーションの追加

  1. Personエンティティを開きます
  2. 下記の追加を行います
    • NotBlank
    @Column(length = 50,nullable = false)
    @NotBlank
    private String firstName;
    
    @Column(length = 50,nullable = false)
    @NotBlank
    private String lastName;
    

ハンドラの修正

  1. WebControllerのformメソッドを下記のように修正します
      @RequestMapping(value = "/", method=RequestMethod.POST)
      @Transactional
      public ModelAndView form(
        @ModelAttribute("formModel") @Validated Person person,
        BindingResult result,
        ModelAndView mav) {
        ModelAndView res = null;
    
        System.out.println(result.getFieldErrors());
    
        if(!result.hasErrors()) {
          repository.saveAndFlush(person);
          res = new ModelAndView("redirect:/");
        } else {
          mav.setViewName("index");
          mav.addObject("title","Test page");
          mav.addObject("message","Sorry, error is occured...");
          List<Person> list = repository.findAll();
          mav.addObject("people", list);
          res = mav;
        }
    
        return res;
      }
    
    • @Validated
      • バリデーションチェックのもの
      • エンティティの値をバリデーションチェックするためのもの
      • エンティティの各値を自動的にチェックする
    • バリデーションチェックのエラー
      • BindingResultで結果がわかる
      • hasErrorsメソッドでエラー有無がわかる

テンプレートの修正

  1. index.htmlを修正します
  <form method="post" action="/" th:object="${formModel}">
    <ul>
      <li th:each="error : ${#fields.detailedErrors()}" class="text text-danger" th:text="${error.message}" />
    </ul>

動作確認

  1. ビルドとSpring Bootを起動します

    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080にアクセスします
    image.png

  3. 空白のまま、「Create」をクリックします。エラーメッセージが表示されました
    image.png

  4. test-taroとtest-suzukiを入力し、「Create」を入れると登録されました

エラーメッセージの位置を変更する

各フィールド配下に、エラーメッセージを表示したほうがわかりやすいので改善します

  1. index.htmlを下記のように修正します
      <form method="post" action="/" th:object="${formModel}">
        <style>.err {color:red}</style>
    
        <div class="mb-3">
          <label for="name" class="form-label">FirstName</label>
          <input type="text" class="form-control" name="firstName" th:value="*{firstName}" th:errorclass="err" />
          <div th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" th:errorclass="err"></div>
        </div>
        <div class="mb-3">
          <label for="name" class="form-label"></label>LastName</label>
          <input type="text" class="form-control" name="lastName" th:value="*{lastName}" th:errorclass="err" />
          <div th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" th:errorclass="err"></div>
        </div>
        <div class="mb-3">
          <input type="submit" class="btn btn-primary" value="Create" />
        </div>
      </form>
    

動作確認

  1. ビルドとSpring Bootを起動します

    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080にアクセスします
    image.png

  3. 空白のまま、「Create」をクリックします。エラーになりました
    image.png

  4. FirstNameだけ入力します。エラーになりました
    image.png

エラーメッセージのカスタマイズ

バリデーションの引数を入れることで、メッセージをカスタマイズできます。

  1. バリデーションのアノテーションにメッセージを指定します
        @Column(length = 50,nullable = false)
        @NotBlank(message="名前(FirstName)を入力してください!")
        private String firstName;
    
        @Column(length = 50,nullable = false)
        @NotBlank(message="名字(LastName)を入力してください!")
        private String lastName;
    

動作確認

  1. ビルドしSpring Bootを再実行します
    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080にアクセスします
  3. 何も入力せずに、「Create」をクリックすると、カスタマイズ後のエラーメッセージが表示されました
    image.png

エラーメッセージのカスタマイズ(プロパティファイル)

エラーメッセージをハードコードするのではなく、プロパティファイルに書いておきます。

  1. 先ほどのmessageを削除しておきます(エンティティ内のmessageが優先されるため)

    @Column(length = 50,nullable = false)
    @NotBlank
    private String firstName;
    
    @Column(length = 50,nullable = false)
    @NotBlank
    private String lastName;
    
  2. ValidationMessages.propertiesをresourcesフォルダに配置します
    image.png

  3. 下記の設定を追記します

    jakarta.validation.constraints.NotBlank.message=must not be blank
    

動作確認

  1. ビルドしSpring Bootを再実行します

    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080にアクセスします

  3. 何も入力せずに、「Create」をクリックすると、プロパティファイルに設定されたエラーメッセージが表示されました
    image.png

バリデータの実装

Validator

PhoneValidatorを実装する

  1. ハンドラーと同様のパッケージにPhoneValidator.javaを追加します
    image.png

  2. 下記のコードを追記します

    package com.example.data.rest_db;
    
    import jakarta.validation.ConstraintValidator;
    import jakarta.validation.ConstraintValidatorContext;
    
    public class PhoneValidator implements ConstraintValidator<Phone, String> {
    
        @Override
        public void initialize(Phone phone) {
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
          return value != null && value.matches("[0-9()-]*");
        }
    
    }
    
    • ConstraintValidatorインタフェイスの実装
      • パラメータは、Phoneと、String
        • Phoneがアノテーション
        • Stringが入力値
      • Stringを入力し、Phoneアノテーションによりバリデーションが設定される
      • initialize
        • 初期化メソッド
        • アノテーションの情報を取得できる
      • isValid
        • バリデーション
        • Stringで入力値、ConstraintValidatorContextが渡される

アノテーション

  1. ハンドラーと同様のパッケージに Phone.javaを追加します
    image.png

  2. 下記のコードを追記します

    package com.example.data.rest_db;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import jakarta.validation.Constraint;
    import jakarta.validation.Payload;
    
    @Documented
    @Constraint(validatedBy = PhoneValidator.class)
    @Target({ElementType.METHOD, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Phone {
    
      String message() default "Invalid phone number";
    
      Class<?>[] groups() default {};
    
      Class<? extends Payload>[] payload() default {};
    
    }
    
    

バリデータを使用する

テンプレートの修正

Phoneバリデータを使用します

  1. index.htmlを修正します。Phoneを追加します
    <body class="container">
      <h1 class="display-4 mb-4" th:text="${title}"></h1>
      <p th:text="${message}"></p>
      <form method="post" action="/" th:object="${formModel}">
        <style>.err {color:red}</style>
    
        <div class="mb-3">
          <label for="name" class="form-label">FirstName</label>
          <input type="text" class="form-control" name="firstName" th:value="*{firstName}" th:errorclass="err" />
          <div th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" th:errorclass="err"></div>
        </div>
        <div class="mb-3">
          <label for="name" class="form-label"></label>LastName</label>
          <input type="text" class="form-control" name="lastName" th:value="*{lastName}" th:errorclass="err" />
          <div th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" th:errorclass="err"></div>
        </div>
        <div class="mb-3">
          <label for="name" class="form-label"></label>Phone</label>
          <input type="text" class="form-control" name="phone" th:value="*{phone}" th:errorclass="err" />
          <div th:if="${#fields.hasErrors('phone')}" th:errors="*{phone}" th:errorclass="err"></div>
        </div>
        <div class="mb-3">
          <input type="submit" class="btn btn-primary" value="Create" />
        </div>
      </form>
    
      <table class="table">
        <thread>
          <tr>
            <th>firstName</th>
            <th>lastName</th>
            <th>Phone</th>
          </tr>
        </thread>
        <tbody>
          <tr th:each="person : ${people}">
            <td th:text="${person.firstName}"></td>
            <td th:text="${person.lastName}"></td>
            <td th:text="${person.phone}"></td>
          </tr>
        </tbody>
      </table>
    </body>
    

エンティティの修正

  1. phone変数と、アクセスメソッド(SetterとGetter)を追加します。また、 作成した@Phoneアノテーションを追加します
        @Column(nullable = false)
        @Phone
        private String phone; // Add this field
    
        public String getPhone() {
          return phone;
        }
        public void setPhone(String phone) {
          this.phone = phone;
        }
    

初期値の設定

  1. ハンドラのinitを修正します。phoneの初期値を埋めます
      public void init() {
        Person p1 = new Person();
        p1.setFirstName("Ichiro");
        p1.setLastName("Suzuki");
        p1.setPhone("00-0000-0000");
        repository.saveAndFlush(p1);
    
        Person p2 = new Person();
        p2.setFirstName("Taro");
        p2.setLastName("Yamada");
        p2.setPhone("00-0000-0001");
        repository.saveAndFlush(p2);
    
        Person p3 = new Person();
        p3.setFirstName("Hanako");
        p3.setLastName("Yamada");
        p3.setPhone("00-0000-0003");
        repository.saveAndFlush(p3);
    
      }
    

動作確認

  1. ビルドしSpring Bootを再実行します

    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080にアクセスします
    image.png

  3. Phoneに英字を入れます。エラーになりました
    image.png
    image.png

考察

今回は、Spring Bootにおけるバリデーションについて、標準で用意されているものの利用から、エラーメッセージのカスタマイズ、バリデーションの自作まで試してみました。
引き続き、SpringBootの機能について学んでいきます。

参考

Spring Boot 3 プログラミング入門

1
2
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
1
2