Spring Boot - пользовательское ограничение Hibernate не внедряет службу

Я постараюсь не обращать внимания на другие детали и кратко:

@Entity
public class User
    @UniqueEmail
    @Column(unique = true)
    private String email;
}

@Component
public class UniqueEmailValidatior implements ConstraintValidator<UniqueEmail,String>, InitializingBean {

    @Autowired private UserService userService;

    @Override
    public void initialize(UniqueEmail constraintAnnotation) {

    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (userService == null) throw new IllegalStateException();
        if (value == null) return false;
        return !userService.isEmailExisted(value);
    }

}

Это будет работать, когда проверка будет выполнена в Spring (Spring MVC @Valid или внедрить Validator с помощью @Autowire), все будет в порядке. Но как только я сохраню сущность с помощью Spring Data JPA:

User save = userRepository.save(newUser);

Hibernate попытается создать экземпляр нового UniqueEmailValidatior без внедрения bean-компонента UserService. Итак, как я могу заставить Hibernate использовать мой компонент UniqueEmailValidatior без создания нового экземпляра. Я мог бы отключить проверку гибернации с помощью spring.jpa.properties.javax.persistence.validation.mode=none, но я надеюсь, что есть другой способ

Обновление: вот мой UserService:

   @Autowired private Validator validator;
   @Transactional
    public SimpleUserDTO newUser(UserRegisterDTO user) {
        validator.validate(user);
        System.out.println("This passes");
        User newUser = new User(user.getUsername(),
                passwordEncoder.encode(user.getPassword()),user.getEmail(),
                "USER",
                user.getAvatar());
        User save = userRepository.save(newUser);
        System.out.println("This won't pass");
        return ....
    }

Вы что-то делаете, чтобы переопределить / настроить LocalValidatorFactoryBean? Hibernate должен использовать то же самое, что и Spring, и поэтому автоматическое подключение должно работать. Или вы явно настраиваете что-то для Spring Data JPA?

M. Deinum 07.05.2018 12:48

Я ничего не отменяю в своей конфигурации, вы также можете проверить обновленную часть

cdxf 07.05.2018 12:55

Я не вижу конфигурации в вашем обновлении (только услуга)?

M. Deinum 07.05.2018 12:57

Я позволяю Spring Boot автоматически настраивать все, что связано с базой данных, поэтому у меня нет конфигурации

cdxf 07.05.2018 13:01

Какую версию Spring Boot вы используете?

M. Deinum 07.05.2018 13:24
2
5
1 877
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ довольно большой, чтобы опубликовать здесь. Пожалуйста, проверьте эту статью в S.O, чтобы помочь вам. Это должно помочь вам начать работу.

Тестирование пользовательского валидатора с помощью Autowired Spring Service

Проблема в том, что спящий режим не знает определения Spring. Однако вы можете сделать так, чтобы Сущности знали о любом типе типов javax.validation. Надеюсь это поможет.

Чтобы компоненты Spring были введены в ваш ConstraintValidator, вам понадобится конкретный ConstraintValidatorFactory, который следует передать при инициализации ValidatorFactory.

Что-то вроде:

ValidatorFactory validatorFactory = Validation.byDefaultProvider()
    .configure()
    .constraintValidatorFactory( new MySpringAwareConstraintValidatorFactory( mySpringContext ) )
    .build();

при этом MySpringAwareConstraintValidatorFactory является ConstraintValidatorFactory, который вводит компоненты в ваш ConstraintValidator.

Я подозреваю, что ValidatorFactory, используемый Spring Data, не вводит валидаторы при их создании, что прискорбно.

Я полагаю, вы сможете отменить это. Или лучше, вы должны открыть проблему с Spring Boot / Spring Data, чтобы они правильно вводили ConstraintValidator, поскольку второй раз подряд у нас есть этот вопрос по SO.

Spring уже делает это с LocalValidatorFactoryBean и более, частью автоматической проводки. Вам не нужен специальный ConstraintValidatorFactory. Вам необходимо сообщить JPA о правильном валидаторе.

M. Deinum 07.05.2018 13:23

Это верно для ValidatorFactory, используемого Spring MVC (и действительно, он работает в случае OP). Мне интересно, если это ValidatorFactory, переданный в Hibernate ORM для проверки JPA.

Guillaume Smet 07.05.2018 13:52

Не по умолчанию (хотя я ожидал, что Spring Boot сделает это). Однако позволить Hibernate (или JPA, если на то пошло) использовать его не очень сложно.

M. Deinum 07.05.2018 13:53
Ответ принят как подходящий

Я ожидал, что Spring Boot подключит существующий валидатор к EntityManager, по-видимому, это не так.

Вы можете использовать HibernatePropertiesCustomizer и добавить свойства к существующему EntityManagerFactoryBuilder и зарегистрировать Validator.

ПРИМЕЧАНИЕ: Я предполагаю, что вы используете Spring Boot 2.0

@Component
public class ValidatorAddingCustomizer implements HibernatePropertiesCustomizer {

    private final ObjectProvider<javax.validation.Validator> provider;

    public ValidatorAddingCustomizer(ObjectProvider<javax.validation.Validator> provider) {
        this.provider=provider;
    }

    public void customize(Map<String, Object> hibernateProperties) {
        Validator validator = provider.getIfUnique();
        if (validator != null) {
            hibernateProperties.put("javax.persistence.validation.factory", validator);
        }
    }
}

Что-то вроде этого должно связать существующий валидатор с гибернацией, и при этом он будет использовать автоматическое подключение.

ПРИМЕЧАНИЕ: Вам не нужно использовать @Component на валидаторе, автоматическое подключение встроено в фабрику валидатора перед возвратом экземпляра Validator.

Я добавил ValidatorAddingCustomizer, но он все еще не работает. Я обновил образец проекта, если вам интересно.

cdxf 07.05.2018 13:54

Вы можете попробовать отладку и посмотреть, вызывается ли это (вы поместили его в пакет, доступный для сканирования компонентов?). Я делал getIfUnique, может быть, нету уникального (а множественного). Вместо этого вы можете попробовать getObject и посмотреть, работает он или нет.

M. Deinum 07.05.2018 14:00

Он вызывается, валидатором является LocalValidatorFactoryBean, но каким-то образом UserService все еще не вводится

cdxf 07.05.2018 14:10

Есть ли шанс, что кто-то, знакомый с экосистемой Spring, может открыть проблему для Spring / Spring Data? Как упоминалось в моем ответе ниже, эта проблема возникает второй раз подряд. Было бы хорошо, если бы это можно было исправить раз и навсегда.

Guillaume Smet 07.05.2018 14:34

Кто угодно может зарегистрировать проблему Github ... Я бы начал с Spring Boot, чтобы она была включена в нее (или, по крайней мере, чтобы это стало возможным).

M. Deinum 07.05.2018 14:56

При поиске этой проблемы я обнаружил весеннюю проблему, которая, вероятно, связана с этой проблемой: github.com/spring-projects/spring-boot/issues/15829. Там может быть полезно отслеживать прогресс.

niknetniko 22.04.2019 01:09

Другие вопросы по теме