

### 実現したいこと

※teratailでも同様の質問をしています。ご了承ください。  
<https://teratail.com/questions/319227>

現在Spring Bootを使用して予約サイトを作成しています。  
今実現したいことは、自作のバリデーションを作成し、ユーザー登録を行う際にユーザーID・パスワードが既に登録されていないかをチェックし登録されていたらエラーメッセージを表示させたいです。

開発環境: Eclipse  
Javaのバージョン: 11  
Spring Bootのバージョン: 2.4.0

### 発生している問題・エラーメッセージ

ConstraintValidatorを実装したUserIdDuplicateCheckValidatorを作成しましたが、そこで@AutowiredしてインジェクトしたUserServiceインスタンスがnullになってしまいます。

[code]

    java.lang.NullPointerException: null
        at mrs.app.user.UserIdDuplicateCheckValidator.isValid(UserIdDuplicateCheckValidator.java:30) ~[classes/:na]
        at mrs.app.user.UserIdDuplicateCheckValidator.isValid(UserIdDuplicateCheckValidator.java:1) ~[classes/:na]
        at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:180) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:62) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:75) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:130) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:123) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:555) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:518) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:488) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:450) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:400) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:172) ~[hibernate-validator-6.1.6.Final.jar:6.1.6.Final]
        at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:116) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
        at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreUpdate(BeanValidationEventListener.java:88) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
        at org.hibernate.action.internal.EntityUpdateAction.preUpdate(EntityUpdateAction.java:304) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
        at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:174) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
        at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
        at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
        at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:na]
        at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
        at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    
    
[/code]

### 該当のソースコード

UserIdDuplicateCheck

[code]

    package mrs.app.user;
    
    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 javax.validation.Constraint;
    import javax.validation.Payload;
    
    @Documented
    @Constraint(validatedBy = { UserIdDuplicateCheckValidator.class })
    @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UserIdDuplicateCheck {
    
        String message() default "{mrs.app.reservation.UserIdDuplicateCheckValidator.message}";
    
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
    
    
        @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
                ElementType.PARAMETER })
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public @interface List {
            UserIdDuplicateCheck[] value();
        }
    }
    
[/code]

UserIdDuplicateCheckValidator

[code]

    package mrs.app.user;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    import mrs.domain.model.User;
    import mrs.domain.service.user.UserService;
    
    
    public class UserIdDuplicateCheckValidator
            implements ConstraintValidator<UserIdDuplicateCheck, User> {
    
        private String message;
    
        @Autowired
        UserService userService; ★★ここが@Autowiredしてもnullになる★★
    
        @Override
        public void initialize(UserIdDuplicateCheck constraintAnnotation) {
            message = constraintAnnotation.message();
        }
    
        @Override
        public boolean isValid(User value, ConstraintValidatorContext context) {
            String userId = value.getUserId();
            Boolean blResult = userService.checkDuplicate(userId);
    
            if(!blResult) {
                context.disableDefaultConstraintViolation();
                context.buildConstraintViolationWithTemplate(message).addPropertyNode("userId").addConstraintViolation();
            }
    
            return true;
        }
    }
    
[/code]

UserService

[code]

    package mrs.domain.service.user;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.DataAccessException;
    import org.springframework.stereotype.Service;
    
    import mrs.domain.model.User;
    import mrs.domain.repository.user.EditUserRepositoryJdbc;
    import mrs.domain.repository.user.UserRepository;
    
    @Service
    public class UserService {
    
        @Autowired
        EditUserRepositoryJdbc userRepositoryJdbc;
    
        @Autowired
        UserRepository userRepository;
    
        public void saveOrUpdate(User user, String userId) throws DataAccessException {
            System.out.println("beforeFix: " + userId);
            System.out.println("fixed: " + user.getUserId());
    
            userRepositoryJdbc.update(user, userId);
        }
    
        public boolean checkDuplicate(String userId) {
            if (userRepository.countByUserId(userId) != 0) {
                return false;
            }
    
            return true;
        }
    }
    
[/code]

UserRepository

[code]

    package mrs.domain.repository.user;
    
    import javax.persistence.LockModeType;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Lock;
    import org.springframework.stereotype.Repository;
    
    import mrs.domain.model.User;
    
    @Repository
    public interface UserRepository extends JpaRepository<User, String> {
    
        @Lock(LockModeType.PESSIMISTIC_WRITE)
        User findOneForUpdateByUserId(String userId);
        long countByUserId(String userId);
    
    }
    
[/code]

User

[code]

    package mrs.domain.model;
    
    import java.io.Serializable;
    
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.Pattern;
    import javax.validation.constraints.Size;
    
    import org.hibernate.validator.constraints.Length;
    
    import mrs.app.user.UserIdDuplicateCheck;
    
    
    //会議室予約システムを利用するユーザー情報を格納するテーブル
    //Userエンティティー
    @Entity
    @Table(name = "usr")
    @UserIdDuplicateCheck(message="このユーザーIDは既に登録されています")
    public class User implements Serializable {
    
        @Id
        @NotBlank(groups=ValidGroup1.class)
        @Email(groups=ValidGroup2.class)
        private String userId;
    
        @NotBlank(groups=ValidGroup1.class)
        @Length(min=4, max=100, groups=ValidGroup2.class)
        private String password;
    
        @NotBlank(groups=ValidGroup1.class)
        private String firstName;
    
        @NotBlank(groups=ValidGroup1.class)
        private String lastName;
    
        /**
        * 電話番号
        */
        @NotBlank(groups=ValidGroup1.class)
        @Pattern(regexp = "0\\d{1,4}\\d{1,4}\\d{4}", groups=ValidGroup2.class)
        private String phoneNumber;
    
        @NotBlank(groups=ValidGroup1.class)
        private String zipCode;
    
        /**
        * 住所
        */
        @NotBlank(groups=ValidGroup1.class)
        @Size(max = 200, groups=ValidGroup2.class)
        private String address;
    
        //@Enumerated(EnumType.STRING)
        @NotBlank(groups=ValidGroup1.class)
        private String roleName;
    
    
        public String getUserId() {
            return userId;
        }
    
        public void setUserId(String userId) {
            this.userId = userId;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getRoleName() {
            return roleName;
        }
    
        public void setRoleName(String roleName) {
            this.roleName = roleName;
        }
    
        public String getPhoneNumber() {
            return phoneNumber;
        }
    
        public void setPhoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
        }
    
        public String getZipCode() {
            return zipCode;
        }
    
        public void setZipCode(String zipCode) {
            this.zipCode = zipCode;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
    }
    
[/code]

### 試したこと

<https://medium.com/@ssscripting/getting-autowired-fields-to-work-with-
constraintvalidators-in-spring-boot-74424d5b0aaa>

上記のサイトをもとにvalidationConfigクラスを作成し、Applicationクラスと同じパッケージ内に配置しました。の設定を行いましたが、nullのままでした。(正直に言うとあまりこの、validationConfigクラスが何を行っているのか理解しきれていません)  
その後もひたすらググってみましたが、解決に至らずここで質問をさせて頂きます。  
よろしくお願いいたします。


CC BY-SA 4.0
2021-01-29T03:35:38.953
73671
2023-11-21T13:00:30.117
2021-01-30T04:07:07.280
43244
43244
question
java spring-boot
@AutowiredでインジェクトしたUserServiceがnullになってしまう


以下のようにUserクラスにある自作バリデーションの第二引数に、ValidGroupクラスを持たせるとエラー無く正常に動作しました。正直なぜValidGroupクラスを指定しないとValidatorクラスで@Autowiredされないのか理解していません。Springの仕様を理解しきれていないので自分でも調べていきますが、ご存じの方はもしよければご教授お願いします。m(__)m

[code]

    //Userエンティティー
    @Entity
    @Table(name = "usr")
    @UserIdDuplicateCheck(message="このユーザーIDは既に登録されています", groups=ValidGroup2.class)
    public class User implements Serializable {
    
    
[/code]


CC BY-SA 4.0
2021-02-01T00:50:04.410
73728
2021-02-01T00:50:04.410
43244
73671
answer


行いたいのは

  * [Spring BootでカスタムValidatorにInjectionしたい時の設定 - Qiita](https://qiita.com/tag1216/items/76b8c1087d6b4b83550b)

の後半部分だと思いますが、それは、次の設定で実現できます。  
(リンク先の方法でも実現できるとは思いますが)

[code]

    import java.util.Map;
    import javax.validation.Validator;
    import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MyConfig {
    
        @Bean
        public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(final Validator validator) {
            return new HibernatePropertiesCustomizer() {
                @Override
                public void customize(Map<String, Object> hibernateProperties) {
                    hibernateProperties.put("javax.persistence.validation.factory", validator);
                }
            };
        }
    }
    
[/code]

* * *

なお、[前述Qiita記事](https://qiita.com/tag1216/items/76b8c1087d6b4b83550b)の前半部分や質問文中でリンクされている[こちらの記事](https://medium.com/@ssscripting/getting-
autowired-fields-to-work-with-constraintvalidators-in-spring-
boot-74424d5b0aaa)の設定は、現行 Spring Boot では `spring-boot-starter-validation`
を依存関係に追加すれば [`ValidationAutoConfiguration`](https://github.com/spring-
projects/spring-boot/blob/v2.5.5/spring-boot-project/spring-boot-
autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java)
によって自動設定されます(ので手動で設定する必要はないはずです)。


CC BY-SA 4.0
2021-10-11T01:26:28.950
82999
2021-10-18T06:49:37.837
2021-10-18T06:49:37.837
2808
2808
73671
answer