バリデーションを自作する方法を学んだので、備忘として本記事を作成しました。
使用環境
- Windows10 (64bit)
- spring-boot : 2.7.1
- postgres : 11.15
ユースケース
ユーザーを新規会員登録させるときに、emailとpasswordの入力が必須。
その際に既に登録してあるemailで新規会員登録できないようにしたいので、
入力されたemailが既にDBに登録があった場合はエラーとなるように、バリデーションを自作する。
バリデーションの名前は@UniqueEmail
とする。
エンティティクラスの作成
今回、ユーザー情報はusersテーブルに格納しておくので、
そのテーブルに対応するuser
クラスを作成する。
user.java
package com.example.domain;
import文省略
public class User {
private int id;
private String email;
private String password;
constructor,getter,setter 省略
}
リポジトリクラスの作成
emailが既に登録されているかどうかDBに問い合わせるリポジトリクラスUserRepository.java
を作成する。
今回は、Spring JDBCを用いてDBにアクセスする。
UserRepository.java
package com.example.repository;
import文省略
@Repository
public class UserRepository {
@Autowired
private NamedParameterJdbcTemplate template;
private static final RowMapper<User> USER_ROW_MAPPER = (rs, i) ->{
User user = new User();
user.setId(rs.getInt("id"));
user.setEmail(rs.getString("email"));
user.setPassword(rs.getString("password"));
return user;
};
/**
* emailからユーザーを特定する
* @param email
* @return
* 既に登録があった場合:emailを返却
* 登録がなかった場合:EmptyResultDataAccessExceptionをスロー
*/
public String findByEmail(String email) throws EmptyResultDataAccessException{
String sql = "SELECT email FROM users WHERE email = :email";
SqlParameterSource param = new MapSqlParameterSource().addValue("email", email);
return template.queryForObject(sql, param, USER_ROW_MAPPER);
}
UniqueEmailクラスを作成
本件の肝です。
今回、バリデーションの名前を@UniqueEmail
にしたいので、UniqueEmail
クラスを作成します。
✓ポイント
- クラスの宣言に、
@interface
と付ける - クラスに下記アノテーションをつける
-
@Target
: アノテーションが付与できる場所の指定を行う -
@Retention
:いつまでアノテーションを残すか設定する -
@Constraint
:バリデーションを実行するクラスを指定する
-
UniqueEmail.java
package com.example.validation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target({ ElementType.METHOD, ElementType.FIELD }) // アノテーションが付与できる場所の指定。今回は、メソッドとフィールドにアノテーションを付与できると指定。
@Retention(RetentionPolicy.RUNTIME) // いつまでアノテーションを残すか。この記述はRuntimeは実行時まで残すということ。
@Constraint(validatedBy = UniqueEmailValidator.class) // バリデーションを実行するクラスを指定。今回はUniqueEmailValidatorクラス で使用する。UniqueEmailValidatorクラスは後程実装する。
public @interface UniqueEmail { // クラスの宣言に、@interface と付ける
// バリデーションが働いた時のエラーメッセージをここで指定する。
String message() default "このEmailは既に使われています。別のメールアドレスを入力してください。";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
バリデータークラスを作成
本件の肝2です。
✓ポイント
-
ConstraintValidator
インターフェイスをimplementsして、isValid
メソッドを実装する
UniqueEmailValidator.java
package com.example.validation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.repository.UserRepository;
// ConstraintValidator<A,T>インターフェイスをimplementsする。Aには先ほど作成したUniqueEmailクラスを指定する。
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String>{
@Autowired
private UserRepository userRepository;
/**
* isValidメソッドを実装し、このなかでバリデーションエラー判定を行う
* @return
* emailに重複がなかった場合:true
* emailに重複があった場合:false
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
try {
userRepository.findByEmail(value);
return false;
} catch (Exception e) {
return true;
}
}
}
フォームクラスを作成する
ユーザーからの入力値を受け取るフォームクラスUserForm
を作成する。
先ほど作成した@UniqueEmail
をここで使用する。
UserForm.java
package com.example.form;
import com.example.validation.UniqueEmail;
public class UserForm {
@UniqueEmail // 自作のバリデーターを使えるようになる
private String email;
private String password;
constructor,getter,setter 省略
}
終わりに
自作のバリデーターを使いたくなる場面は結構出てくるかと思うので、備忘のために執筆しました。
間違っている部分もあるかと思いますので、その際はご指摘頂ければと思います。
参考文献
【Spring Boot】バリデーション
Spring Frameworkで使用できるBean Validationアノテーション一覧