Springのクロスフィールド検証とは?複数項目の整合性チェックをやさしく解説
先生と生徒の会話形式で理解しよう
生徒
「Springで、2つの入力値が一致しているか確認したいときって、どうやってチェックするんですか?」
先生
「例えばパスワードと確認用パスワードのように、複数のフィールドをまとめてチェックするには、クロスフィールド検証を使う方法がありますよ。」
生徒
「それって、@NotNullとかみたいにアノテーションでできるんですか?」
先生
「はい、独自のアノテーションとバリデータークラスを作れば可能です。実際にコードを見ながら、クロスフィールド検証のやり方を学んでいきましょう。」
1. クロスフィールド検証とは?
クロスフィールド検証とは、複数のフィールドをまたいで整合性チェックを行うバリデーションのことです。Springでは通常、@NotNullや@Sizeなど、1つのフィールドに対して単独で検証を行いますが、パスワードと確認パスワードのように、2つのフィールドが一致しているかをチェックするには、独自のバリデーションが必要になります。
2. クロスフィールド検証の仕組み
Springのクロスフィールド検証では、以下のステップで実装を行います:
- 独自のバリデーション用アノテーションを定義する
- Validatorクラス(
ConstraintValidatorの実装)を作成する - エンティティやDTOクラスにアノテーションを付ける
3. バリデーションアノテーションの作成
まずはフィールドAとフィールドBが一致しているかをチェックするアノテーションを作成します。
@Documented
@Constraint(validatedBy = FieldMatchValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldMatch {
String message() default "フィールドの値が一致しません";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String first();
String second();
}
4. バリデータークラスの作成
次に、実際の比較処理を行うバリデータークラスを作ります。
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
@Override
public void initialize(FieldMatch constraintAnnotation) {
this.firstFieldName = constraintAnnotation.first();
this.secondFieldName = constraintAnnotation.second();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
try {
Object first = BeanUtils.getProperty(value, firstFieldName);
Object second = BeanUtils.getProperty(value, secondFieldName);
return first != null && first.equals(second);
} catch (Exception e) {
return false;
}
}
}
5. DTOクラスでの使い方
パスワードと確認用パスワードを入力するDTOで、先ほどのアノテーションを使用します。
@FieldMatch(first = "password", second = "confirmPassword", message = "パスワードが一致しません")
public class SignupForm {
@NotBlank
private String username;
@NotBlank
private String password;
@NotBlank
private String confirmPassword;
// ゲッター・セッター省略
}
6. Springのバリデーションとの連携
Controllerでは、@ValidとBindingResultを使ってエラーを処理します。
@PostMapping("/signup")
public String signup(@Valid SignupForm form, BindingResult result, Model model) {
if (result.hasErrors()) {
return "signup-form";
}
// 登録処理
return "redirect:/signup-success";
}
7. HTMLフォームの例
HTMLフォームでは、パスワードと確認用パスワードの2つの入力欄を作成します。
<form th:action="@{/signup}" th:object="${signupForm}" method="post">
<div class="mb-3">
<label for="username">ユーザー名</label>
<input type="text" th:field="*{username}" class="form-control"/>
<div th:if="${#fields.hasErrors('username')}" th:errors="*{username}" class="text-danger"></div>
</div>
<div class="mb-3">
<label for="password">パスワード</label>
<input type="password" th:field="*{password}" class="form-control"/>
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="text-danger"></div>
</div>
<div class="mb-3">
<label for="confirmPassword">確認用パスワード</label>
<input type="password" th:field="*{confirmPassword}" class="form-control"/>
<div th:if="${#fields.hasErrors('confirmPassword')}" th:errors="*{confirmPassword}" class="text-danger"></div>
</div>
<button type="submit" class="btn btn-primary">登録</button>
</form>
8. よくあるエラーと解決方法
クロスフィールド検証でよくあるトラブルには、以下のようなものがあります:
- BeanUtilsのプロパティ取得失敗:getterが正しく定義されていない場合は例外になります。
- バリデーションが呼ばれない:アノテーションを
@Target(ElementType.TYPE)で指定しているか確認しましょう。 - messageが表示されない:HTML側でバインディングエラーを適切に表示しているかチェックが必要です。
9. クロスフィールド検証の応用例
この仕組みは、以下のような用途にも応用できます:
- 開始日と終了日の前後関係チェック
- メールアドレスと再確認の一致チェック
- 住所情報の都道府県と郵便番号の組み合わせチェック
このように、Springのクロスフィールド検証は柔軟で実務的にも役立つ技術です。