ぺんぎんらぼ

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です。たまに絵を描きます。

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です

Spring Bootで作るWebアプリケーション⑥ - 単項目入力バリデーション

前回では、Spring Bootを使ったWebアプリケーションで入力されたデータをフォームオブジェクトに格納する方法を解説しました。 今回は、このフォームオブジェクトに格納されている入力されたデータを、バリデーション(入力チェック)する解説します。

バリデーションとは

画面から入力されたデータが正しいか検証することが「バリデーション」です。 例えば、名前は必ず入力する必要がある(入力必須)とか、年齢は数値で入力する必要がある、などです。

バリデーションは何種類かに分類することができます。

単純な単項目チェック
項目単位での単純なチェックです。
先ほど例を挙げた入力必須チェックや数値入力チェックなどがこれにあたります。
Spring FrameworkにはSpring Validatorという、単純でよく利用されるであろうチェックパターンを簡単に実装する仕組みが用意されています。

複数の項目の組み合わせによる複数項目チェック
複数の項目の入力値を使うチェックです。
例えば、日付の入力で「月」の項目と「日」の項目を設けた場合、「月」に1月を入力した場合、「日」の入力値は1~31日であるチェックとなりますが、「月」に4月を入力した場合、「日」の入力値は1~30日であるチェックになります。
このように、複数の項目の入力値を組み合わせてチェックをする場合は、チェック用のロジックを実装する必要があります。

ロジックが必要となるチェック
単数・複数項目に限らず、特殊なチェックが必要になる場合です。
例えば、ショッピングサイトで購入個数の項目の場合、データベースを参照して在庫があるかをチェックする必要があります。
このように、単数・複数の項目に限らず、特殊なチェックをする場合は、チェック用のロジックを実装する必要があります。

今回は、この中の単純な単項目チェックの実装方法を解説します。

Spring Validator

バリデーションで使用するSpring Validatorは、さまざまなチェックパターンに対応できる仕組みが用意されていますが、今回は、その中のBean Validationを使用します。
このBean ValidationはSpring独自の仕様ではなく、JSRで仕様が定義されたもので、Javaで正式に策定されたものになります。

Bean Validationで実装されているバリデーションの種類は別ページにまとめています。

penguinlabo.hatenablog.com

バリデーションの実装

では、早速、バリデーションを実装してみます。今回の実装は驚くほど簡単です。
入力項目「名前」の文字数は3文字以上、15文字以下である文字数チェックを実装します。
フォームであるProfileFormクラスのnameフィールドに@Sizeアノテーションを指定します。

    @Size(min = 2, max = 15)
    private Strint name;

次にコントローラであるProfileControllerクラスの修正です。

    @PostMapping("age")
    public String age(@Validated ProfileForm profileForm, BindingResult result) {
        if (result.hasErrors()) {
            return "name";
        }
        return "age";
    }

修正箇所は大きく2か所です。

    public String age(@Validated ProfileForm profileForm, BindingResult result) {

引数のフォームクラスに@Validatedアノテーションを指定します。これによって、フォームクラスに指定したバリデーションアノテーションに従って、バリデーションが実行されます。
そして、BindingResultクラスを引数に追加しています。このクラスは入力値をフォームクラスに格納した結果が格納されており、バリデーションの結果も格納されています。

        if (result.hasErrors()) {
            return "name";
        }

そして、メソッドの最初にBindingResultのhasErrorsメソッドを呼び出しています。バリデーションエラーがあった場合、このメソッドがtrueを返します。
バリデーションエラーがあった場合、再び名前入力画面に遷移しています。

以上です。これだけで入力項目「名前」は入力文字数のチェックが働きます。

実際に動かしてみましょう。

f:id:penguinlabo:20200913012804p:plain

「名前」欄の入力文字数が2文字の状態で「次へ」ボタンを押すとバリデーションが働いで、再び、名前入力画面に戻されているのがわかります。

入力値の復元

バリデーションの実装で、入力エラーを検出した場合、再度、入力画面に戻ることが確認できました。
しかし、以前の入力内容がクリアされていました。この実装では入力エラーで元の画面に戻ったときにすべての入力内容がクリアされてしまいます。例えば、一つの画面で名前、年齢、性別、住所、電話番号・・・など、複数の入力項目があった場合、一つでも入力エラーがあった場合、正常な値が入力された項目も含め、すべての入力値がクリアされてしまいます。

次に入力エラーで入力画面に戻った場合、以前の入力値が復元されるようにします。

名前入力画面の画面テンプレートであるname.htmlを修正します。

<form th:action="@{/profile/age}" th:object="${profileForm}" method="post">
    <p>
        <label for="name">名前を入力してください</label>
        <input type="text" name="name" id="name" th:field="*{name}">

formタグ内でフォームオブジェクトにアクセスしやすいよう、formタグにth:object属性を追加し、値に"${profileForm}"を指定しています。これは、前回の年齢入力画面の修正内容と同じです。
今回の肝はinputタグに追加したth:field属性です。この属性の値にフォームの名前フィールドであるnameを指定することで、入力内容が復元されるようになります。

入力内容の復元は画面テンプレートの修正だけで終了です。

実際に動かしてみましょう。

f:id:penguinlabo:20200913014940p:plain

名前の入力値が復元されているのがわかります。

エラーメッセージの表示

実行した画面を見てもわかる通り、入力エラーがあった場合、どの項目でどのようなエラーを検出したのかがわかりません。
エラーメッセージの表示を実装します。

画面テンプレートを修正して、エラーメッセージが表示されるようにします。

<form th:action="@{/profile/age}" th:object="${profileForm}" method="post">
    <p>
        <label for="name">名前を入力してください</label>
        <input type="text" name="name" id="name" th:field="*{name}">
        <span style="color: red;" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>

inputタグの次にspanタグを追加しています。このspanタグにThymeleafの属性が2つ指定されています。
1つ目はth:if属性で、この属性の属性値に指定した値がtrueになった場合のみ、このspanタグが有効になります。ここでは属性値に"${#fields.hasErrors('name')}"が指定されています。これは、フィールドnameにエラーがあった場合にtrueを返すものです。
2つ目はth:errors属性で、この属性の属性値で指定したフォームのフィールドのエラーメッセージが表示されます。ここでは属性値に"*{name}"が指定されています。これは、フォームのnameフィールドのエラーメッセージを表示する指定となります。

実際に動かしてみましょう。

f:id:penguinlabo:20200913021401p:plain

エラーメッセージが表示されていることがわかります。

エラーメッセージの表示だけでなく、エラーがあった場合、エラー箇所を強調表示したい場合もあるでしょう。エラーを検出したとき、エラーのあった項目の見た目を変更してみましょう。

<title>Name</title>
<style>
.input-error {
    border: 1px solid red;
}
</style>
</head>
<body>
<form th:action="@{/profile/age}" th:object="${profileForm}" method="post">
    <p>
        <label for="name">名前を入力してください</label>
        <input type="text" name="name" id="name" th:field="*{name}" th:errorclass="input-error">
        <span style="color: red;" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>

styleタグでCSSを定義しています。クラス名「input-error」でエラー時のスタイルを定義します。ここでは枠線を赤色で定義しています。
そして、inputタグにth:errorclassで、エラー時に適用されるクラス名を指定します。先ほど定義したクラス名「input-error」を指定します。

再度、動かしてみましょう。

f:id:penguinlabo:20200913023847p:plain

入力項目「名前」の枠線が赤色になっているのがわかります。この方法を使うことで、エラー時の入力項目の見た目を自由にカスタマイズすることができます。

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

これまでの実装で、入力チェックのチェックロジックを実装をすることなく、入力チェックからエラーメッセージの表示がされました。
しかし、表示されるエラーメッセージは英語で既定のメッセージが表示されます。最後に、このエラーメッセージをカスタマイズします。

エラーメッセージはプロパティファイルに定義します。
/src/main/resourcesにValidationMessages_ja_JP.propertiesを以下の内容で作成します。

javax.validation.constraints.Size.message={min}文字から{max}文字で入力してください。

ファイル名を見てもわかる通り、ロケールが日本の場合、このプロパティファイルのメッセージが使用されます。デフォルトではOSのロケールJavaに引き継がれるので、例えば、日本語のWindowsで実行すれば、このプロパティのエラーメッセージが使用されます。

実際に動かしてみましょう。

f:id:penguinlabo:20200913030249p:plain

プロパティファイルに定義したエラーメッセージが表示されています。

次回予告

今回は画面の入出力項目の単項目バリデーションを実装しました。 チェックロジックを実装することなく、Spring ValidationのBean Validationを使用することで、簡単にバリデーションが実装できました。簡単に実装できるだけでなく、エラー表示のカスタマイズの自由度も高いものです。

「Spring Bootで作るWebアプリケーション」シリーズは次回で最終回の予定です。
では、次回の「Spring Bootで作るWebアプリケーション⑦ - 入力ロジックによるバリデーション」でお会いしましょう!!