Spring Boot et Bean Validation : injecter un repository dans un Validator custom

April 02, 2021

Introduction

La spécification Bean Validation, dont l’implémentation est Hibernate Validator, permet de valider si certains champs sont null, la longueur des champs, etc. C’est bien pratique car ça évite de devoir réimplémenter la roue, une seul annotation par exemple @NotNull suffit pour dire que le champ annoté de ne doit pas être null.

On peut aller plus loin et faire une classe de validation personnalisée. Par exemple, vérifier qu’un mail n’est pas existant en base avant une inscription.

La spécification nous permet de mettre en place un tel controle personnalisé, c’est ce que nous allons voir en 1/.

Le problème c’est qu’on souhaite avoir un repository Spring Data dans cette classe de validation, afin d’interrgoer la base de données. Et là on un soucis, que je vais vous présenter en 2 . On verra une solution (perfectible) en 3/ .

1 - Mise en place de notre contrôle personnalisé

Pour mettre en place notre contrôle de vérification d’e-mail, on va tout d’abord importer la librairie Bean Validation dans le pom.xml:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

Ensuite on va mettre en place une annotation particulière, @EmailExisting, qu’on va placer sur le champ email de notre entité, pour lancer le contrôle. Ce contrôle est stocké dans la classe EmailValidator. L’annotation @Constraint permet le lien entre l’annotation et la classe EmailValidator :

@Documented
@Constraint(validatedBy = EmailValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EmailExisting {
    String message() default "Email already exists";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Enfin on code notre controle de vérification d’email, avec la déclaration de notre repository Spring Data pour interroger la base:

public class EmailValidator implements
        ConstraintValidator<EmailExisting, String> {

    @Autowired
    UserRepository userRepository;

    @Override
    public boolean isValid(String email,
                           ConstraintValidatorContext cxt) {
        List<User> users = userRepository.findByEmail(email);
        if (!users.isEmpty()) {
            return false;
        }
        return true;
    }

}

IL faut aussi déclarer @Valid dans le controlleur MVC REST pour activer le controle :

    @PostMapping(value = "/users")
    public ResponseEntity addUSer(@Valid @RequestBody User user, HttpServletResponse response) {

        user.setEmail(user.getEmail());
        user.setLastName(StringUtils.capitalize(user.getLastName()));
        user.setFirstName(StringUtils.capitalize(user.getFirstName()));
        userRepository.save(user);

        return new ResponseEntity(user, HttpStatus.CREATED);
    }

Problème : l’injection du repository est null!

Testons cela : lancer le serveur, et appeler le endpoint sur http://localhost:8080/users avec par exemple Postman. Il faudra indiquer un requestBody adapté, par exemple :

{
email: "tata@yopmail.com",
firstName: "tata",
lastName: "tata",
password: "tatata"
}

Le contrôle se déclenche deux fois :

  • une première fois dans la couche controlleur par SPRING MVC via l’annotation @Valid. Aucun problème, le controle se passe bien ;
  • une deuxième fois lorsque l’on tente de sauvegarder le User via la méthode save() de Spring JPA. On rappelle à nouveau le contrôle d’email, et là le repository devient null, pour une raison que j’ignore! Ca implique une hideuse NullPointerException!

Solution temporaire

La solution temporaire trouvée est tout simplement de désactiver le contrôle lors de l’enregistrement. Ca fait sens, vu qu’on a déjà fait le contrôle juste avant dans la couche Controlleur.

Il faut ajouter au fichier de configuration Spring Boot application.properties :

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

Et bingo, ça marche cette fois, mais on ne rentre qu’une seule fois dans le controle de validation, via @Valid et Spring MVC. Si vous avez une idée comment injecter le repository et permettre ainsi de valider deux fois, je suis preneur!

Le code est accessible ici :https://github.com/smaestri/poc-spring-validation

Cheers,

PS : Très bons articles qui m’ont été utiles :

et stackoverflow:


Vous voulez apprendre ou améliorer vos compétences avec Spring (Core, MVC, Data, Security) et React?
Suivez ma formation sur Udemy où nous apprendrons les fondamentaux et développerons ensemble une application pas à pas!