2

I created a custom validation rule to check if a username exists in the database.

My User class has a username that has a custom validation rule that when an object is created it checks in the database if that same username exists.

I use an interface UserRepository extends JpaRepository<User, Integer> to save the users to the database and in the custom validation rule to check if the username already exists. I make use of

@Autowired
UserRepository userRepository;

Separately I can validate users and save them to the database, but when using them together like calling userRepository.save(user); the @Autowired UserRepository userRepository; in the custom validation rule does not get initiated so it remains null.

javax.validation.ValidationException: HV000028: Unexpected exception during isValid call.

Custom validation rule

public class UsernameConstraintValidator implements ConstraintValidator<Username, String> {
    
    @Autowired
    UserRepository userRepository;

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return !userRepository.existsUserByUsername(s);
    }
}

Trying to save user in the controller

  @PostMapping("/save")
    public String save(@Valid @ModelAttribute("user") User user,BindingResult result, @RequestParam String password2){

        if(result.hasErrors()) return "register";

       userRepository.save(user);
               
        return "login";
    }

An illustration of my problem

overheated
  • 310
  • 4
  • 11

2 Answers2

4

Add spring.jpa.properties.javax.persistence.validation.mode=none in application.propperties

I found the answer in this thread: Spring-Boot How to properly inject javax.validation.Validator

This answer includes the solution to the problem that I was having with bean validation and hibernate.

"...because Hibernate doesn't know about the Spring Context and as far as I can tell there is no way to tell it, not even with the LocalValidatorFactoryBean. This causes the Validator's to run twice. One correctly, and once that fails."

In short the solution is to tell hibernate to not run the validation by adding the following line in your application.propperties

spring.jpa.properties.javax.persistence.validation.mode=none

I suggest you read the full answer of this question to better understand it's implications with LocalValidatorFactoryBean.

Credits also go to João Dias for helping me find this answer.

overheated
  • 310
  • 4
  • 11
0

Your Custom Validation class does not seem a Spring managed bean. Try adding @Component annotation to it as follows:

@Component
public class UsernameConstraintValidator implements ConstraintValidator<Username, String> {
    
    @Autowired
    UserRepository userRepository;

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return !userRepository.existsUserByUsername(s);
    }
}

Update 06/09/2021 As it seems (according to https://stackoverflow.com/a/13361762/16572295) you also need to setup a LocalValidatorFactoryBean to have this working:

@Bean
public Validator validatorFactory() {
    return new LocalValidatorFactoryBean();
}
João Dias
  • 16,277
  • 6
  • 33
  • 45
  • I already did try that, but without success. I also tried to change the scope of the UserRepository to prototype, also no success. – overheated Sep 06 '21 at 13:46
  • So it seems that you need something else to have it working --> https://stackoverflow.com/a/13361762/16572295. I will add it to my answer. – João Dias Sep 06 '21 at 16:23
  • I don't think that it has anything to do with LocalValidatorFactoryBean(), because the validation works perfectly. I think the problem might be in that Repository, because it's trying to start another process while one is already running, so the userRepo.save(user) is calling the userRepo.existsUserByUsername("username") – overheated Sep 07 '21 at 13:41
  • I am not that sure about `LocalValidatorFactoryBean` either to be honest. But I don't believe it has anything to do with `userRepo.save(user)`. Validation is done before the code reaches that point. And I also don't get the "validation works perfectly" when as you state in the question that "in the custom validation rule does not get initiated so it remains null". – João Dias Sep 07 '21 at 14:03
  • 1
    Nevertheless, everywhere I look, I find stuff like: "In spring if we register `LocalValidatorFactoryBean` to bootstrap `javax.validation.ValidatorFactory` then custom `ConstraintValidator` classes are loaded as Spring Bean. That means we can have the benefit of Spring's dependency injection in validator classes." Which is basically what you are looking for. – João Dias Sep 07 '21 at 14:07
  • This was not exactly what I was looking for, but it led me to more research about `LocalValidatorFactoryBean` that you said and I finally found the correct solution. So according to a stackoverflow answer (link below) _"...because Hibernate doesn't know about the Spring Context and as far as I can tell there is no way to tell it, not even with the `LocalValidatorFactoryBean`. **This causes the Validator's to run twice. One correctly, and once that fails.**"_ https://stackoverflow.com/a/23615478/14547085 Thanks for your help, the LocalValidatorFactoryBean was a good hint. – overheated Sep 08 '21 at 06:16