SpringBootで一意性制約のアノテーションを自作(コピペで完成)
#はじめに
SpringBootでアプリケーションを開発中に、Emailやその他ユニークな値の制約の設定の仕方がわからず、ググったところ、Springのバリデーションでは一意性のエラーチェックがないようなので、自作することになった。
ところが、自分が参考にしている記事を超える記事がなく、小1時間ほど悩んでようやく実装できたので、備忘録として残すことにした。
※Railsならdeviceが自動で実装してくれていた...
#環境
・Java11
・SpringBoot2.5.6
・Spring Data JPA
・MySQL
・gradle
#アノテーションを自作する
まず、下記の画像のようにcom.example.demo
上で右クリックをし、新規→注釈の順でアノテーションインターフェースの雛形を作成する。
次に、実際にアノテーションインターフェースのコードを書いていく。
##アノテーションインターフェース
package パッケージ名;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Documented
@Constraint(validatedBy = UnusedValidator.class) //この後に作成するバリデーションクラスを指定する
@Target({FIELD}) // 項目に対してバリデーションをかける場合はFIELDを選ぶ
@Retention(RUNTIME)
public @interface Unused {
String message() default "すでに登録済みのメールアドレスです"; // エラーメッセージ
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({FIELD})
@Retention(RUNTIME)
@Documented
public @interface List {
Unused[] value(); // インターフェース名[] value()とする
}
}
Class<?>[]・・・
などよくわからない記述も多いと思うが、アノテーションインターフェースはほぼほぼ定型のようなものなので、ここでの解説は割愛する。
##バリデーションクラス
package com.example.demo.validate;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.demo.entity.MUser;
import com.example.demo.service.UserService;
public class UnusedValidator implements ConstraintValidator<Unused, String> {
@Autowired
private UserService service;//各自で設定が必要
public void initialize(Unused constraintAnnotation) {
}
public boolean isValid(String value, ConstraintValidatorContext context) {
//各自で設定が必要
MUser email = service.findByEmail(value).orElse(null); // ここのvalueは入力値
if(email == null){
return true;
}
return false;
}
}
バリデーションクラスではインターフェースをimplementsしてアノテーションの内容を記述していく。
アノテーションの処理内容としては、boolean型
のisValid()メソッド
がアノテーションに入る。
ここで、ServiceクラスのfindByEmail()
を呼び出し、条件分岐により、処理内容を分けている。
##Repositoryクラス
実際の、findByEmail()メソッド
の中身だが、JPAを利用して、DBからemailカラムの値を取得するようにしている。
package com.example.demo.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.entity.MUser;
@Repository
public interface UserRepository extends JpaRepository<MUser, Integer> {
public Optional<MUser> findByEmail(String email);
}
##Serviceクラス
そして、ServiceクラスでfindByEmail()メソッド
を定義しRepositoryのメソッドを返すようにする。
これにより、アノテーションのバリデーションクラスにDBに登録されているメールアドレスを渡すことができる。
package com.example.demo.service;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.entity.MUser;
import com.example.demo.form.SignupForm;
import com.example.demo.repository.UserRepository;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
/**メールアドレスでユーザー検索*/
public Optional<MUser> findByEmail(String email) {
return userRepository.findByEmail(email);
}
}
##実装
これで後は@〇〇
のようにアノテーションを実装するだけで完了だ。
@Unused
private String email;
各自、EntityなりFormなりバリデーションを実装しているクラスがあると思うので、そこで、実装するだけで完了だ。
実際に以下のusersテーブル
に登録されている値を入力してみて確認してみた。
以下の画像にあるように、しっかりアノテーションが実装されていることが確認できた。
筆者のように「一意性制約」などピンポイントでの実装をググる方は結構いると思うが、どの記事も、アノテーションの実装の内容は理解できるが、実際にサービスクラスの中身等まで解説しているのはなく、「各自で実装しろ」と言われても・・・という状態になると思う。
筆者もそうだったため、参考になれば幸いだ。
#参考文献
・Spring BootでBean Validation (3) バリデーション処理を自作
Discussion