Reputation: 5648
I will try to ignore other details and make it short:
@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);
}
}
This will work when the validation is made in Spring (Spring MVC @Valid
or inject the Validator
using @Autowire
), everything will be fine.
But as soon as I save the entity using Spring Data JPA:
User save = userRepository.save(newUser);
Hibernate will try to instantiate a new UniqueEmailValidatior
without inject the UserService
bean.
So how can I make Hibernate to use my UniqueEmailValidatior
component without it instantiate a new one.
I could disable hibernate validation using spring.jpa.properties.javax.persistence.validation.mode=none
but I hope there is another way
Update: Here is my 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 ....
}
Upvotes: 3
Views: 3126
Reputation: 124441
I would expect that Spring Boot would wire the existing validator to the EntityManager
apparently it doesn't.
You can use a HibernatePropertiesCustomizer
and add properties to the existing EntityManagerFactoryBuilder
and register the Validator
.
NOTE: I'm assuming here that you are using 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);
}
}
}
Something like this should wire the existing validator with hibernate and with that it will make use of auto wiring.
NOTE: You don't need to use @Component
on the validator the autowiring is build into the validator factory before returning the instance of the Validator
.
Upvotes: 8
Reputation: 10519
To have the Spring beans injected into your ConstraintValidator
, you need a specific ConstraintValidatorFactory
which should be passed at the initialization of the ValidatorFactory
.
Something along the lines of:
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.constraintValidatorFactory( new MySpringAwareConstraintValidatorFactory( mySpringContext ) )
.build();
with MySpringAwareConstraintValidatorFactory
being a ConstraintValidatorFactory
that injects the beans inside your ConstraintValidator
.
I suspect the ValidatorFactory
used by Spring Data does not inject the validators when creating them, which is unfortunate.
I suppose you should be able to override it. Or better, you should open an issue against Spring Boot/Spring Data so that they properly inject the ConstraintValidator
s as it the second time in a row we have this question on SO.
Upvotes: 1
Reputation: 5786
The answer is quite big to post here. Please check for this article in S.O to help you with. This should help you get started.
Test Custom Validator with Autowired spring Service
The problem is hibernate will no way know spring definition. However you can make Entities to be aware of any type of javax.validation
types. Hope this helps.
Upvotes: 0