2

Problem already solved here but didn't work for me (not same spring boot version i guess) this

Trying to code a custom constraint validator, to check whether account email exists before persisting.

@Email
@NotNull
@NotBlank
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueCompteEmailValidator.class)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface UniqueCompteEmail {

    String message() default "{com.mssmfactory.bacsimulator.uniquecompteemail.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

// --------------------------------------------------------------------------

public class UniqueCompteEmailValidator implements ConstraintValidator<UniqueCompteEmail, String> {

    @Autowired
    private CompteRepository compteRepository;

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && this.compteRepository.findByEmail(value) == null;
    }
}

// --------------------------------------------------------------------------

@Configuration
public class OtherConfigurations {

    //----

    @Bean
    public Validator validator(@Autowired AutowireCapableBeanFactory autowireCapableBeanFactory) {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()
                .constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory))
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }
}

// --------------------------------------------------------------------------

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.mssmfactory</groupId>
    <artifactId>bacsimulator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>bacsimulator</name>
    <description>BAC Simulator</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jersey</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

My problem is that private CompteRepository compteRepository always contains null, and from my stacktrace, i can see that it's being instanciated by hibernate-validation instead of being instanciated by spring and used by hibernate.

Any idea ?

Mssm
  • 717
  • 11
  • 29

2 Answers2

7

Changed hibernate's bean validator factory by this @Component class and now it works so well

@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);
        }
    }
}
Mssm
  • 717
  • 11
  • 29
  • 1
    Is there a solution similar for non Spring boot? I am using Spring Framework 5, Hibernate 5, and Hibernate Validator 6. I've tried a lot of solutions found in Stackoverflow and nothing works yet. – Joe A Jul 17 '19 at 15:16
  • To be honest i don't know i always work with spring-boot :/ – Mssm Jul 18 '19 at 10:31
  • Amazing. I've been hitting my head against this one off and on for weeks! – Dan Feb 06 '20 at 14:39
  • @Mssm Bro, I have followed your approach. But when I am trying to save my entity, my validator's `isValid` method is being called **repeatedly** resulting in stackoverflow error. What am I doing wrong here? https://friendpaste.com/6ppeO6IeXz2lr4D55QDilU – Abhinaba Chakraborty Jul 14 '20 at 13:04
  • @Mssm Here is the stackoverflow question: https://stackoverflow.com/questions/62896519/making-database-call-inside-isvalid-method-of-custom-constraintvalidator-is – Abhinaba Chakraborty Jul 14 '20 at 13:39
  • @JoeA If helps, the thing you want to eventually tweak/fix is the LocalContainerEntityManagerFactoryBean; You can set the value above in it's jpaProperties member – mrusinak May 20 '21 at 18:27
  • @JoeA did you ever figure out how to do this without spring boot? – Toofy Mar 17 '23 at 12:25
0

The issue that you are facing is because the @Autowired annotation is not working for you when you autowire CompteRepository.class.You can autowire components in your validation class but make sure that you use spring to create the validator bean otherwise the bean injection won't work as manually creating validator bean using valdiation factory (Hibernate Validator as reference implentation) does not inject dependencies into ConstraintValidator instances.

So, you cannot use the validator factory to create the validator instance , instead autowire it and everything should work.

@AutoWired
Validator validator;
...

validator.validate(book);
Ananthapadmanabhan
  • 5,706
  • 6
  • 22
  • 39
  • I'm using annotations to validate java pojo fields obtained throw a remote access (basiclly a post request) to an exposed rest repository, i'm not the one who has to call validator.validate(entity), it's called by the validator... – Mssm Jun 27 '19 at 15:56
  • Instead of configuring the validator bean yourself, can you try letting spring handle it . – Ananthapadmanabhan Jun 28 '19 at 03:42
  • If i'm not configuring my validator anymore, i just configure hibernate validator factory so that hibernate doesn't instanciate validators but uses validators that spring intanciated and which are in the the spring context – Mssm Jun 28 '19 at 08:49