Home > Software design >  Spring Boot: repository does not autowire in the custom validator
Spring Boot: repository does not autowire in the custom validator

Time:05-07

I have a custom validator that validates data against DB using repository:

@Constraint(validatedBy = DataValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomValidator {

    String message() default "some message";

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

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

@Component
public class DataValidator implements ConstraintValidator<CustomValidator, String> {

    @Autowired
    private DataRepository repository;

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        var data = repository.findDataByValue(value);
        //validation logic with result in 'isValid' variable
        return isValid;
    }
}

I have entity with a field that is annotated with DataValidator:

@Entity
@Table(name = "custom_data")
public class Data {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @DataValidator
    @NotBlank(message = "Value is mandatory")
    @Column
    private String value;

Spring Boot 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-web</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

When I call repository.save(data) from the rest controller, my validator is called, but its repository field is null.

What configuration did I miss that DataRepository bean was injected to RestController correctly, but wasn't injected into DataValidator?

CodePudding user response:

Try it this way.

@Configuration
public class DataValidator implements ConstraintValidator<CustomValidator, String> {
    
    private static final DataValidator holder = new DataValidator();
    
    @Bean
    public static DataValidator bean(DataRepository repository) {
        holder.repository = repository;
        return holder;
    }

    private DataRepository repository;

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        var data = holder.repository.findDataByValue(value);
        //validation logic with result in 'isValid' variable
        return isValid;
     }
}

CodePudding user response:

I found a solution that will autowire the bean, but you need to call a validator manually.

First of all, add the following to application.properties to disable automatic validation trigger on data persistence:

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

Create @Configuration class and describe Validator bean, configure validator factory for it:

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

    return validatorFactory.getValidator();
}

Add validator to the class where you want to use it and and call its validate method directly:

@RestController
public class DataController {
    @Autowired
    private DataRepository repository;

    @Autowired
    private Validator validator;

    @PostMapping("/doSomething")
    public Data doSomething(@RequestBody Data data) {        
        var validationResult = validator.validate(data);
        //validation result processing
        return repository.save(data);
    }
}

or if you use validator within the REST endpoint as in this example, usage of the @Valid annotation is more correct, to my mind. Then you don't need to declare Validator bean:

@RestController
public class DataController {
    @Autowired
    private DataRepository repository;

    @PostMapping("/doSomething")
    public Data doSomething(@Valid @RequestBody Data data) {                
        return repository.save(data);
    }
}
  • Related