Bean validation est un framework qui permet de valider les données au sein des objets Java (JavaBeans).
Il vous permet de définir des contraintes sur les propriétés de vos objets et s’assure que les données respectent ces contraintes avant d’être utilisées.
Bean Validation est une API Java standard définie par le JSR 380.
Il existe plusieurs implémentations de Bean Validation, dont Hibernate Validator est l’une des plus populaires.
Pourquoi utiliser Bean Validation ?
Améliorer la qualité des données: En s’assurant que les données sont valides avant d’être utilisées, vous réduisez les erreurs et les exceptions.
Simplifier le code: Les annotations de validation rendent le code plus concis et plus lisible.
Améliorer la sécurité: En validant les entrées utilisateur, vous réduisez les risques d’injections de code et d’autres attaques.
Cas d’utilisation courants:
Validation des formulaires: S’assurer que les données saisies par l’utilisateur sont conformes aux attentes.
Validation des données d’entrée d’API: Vérifier la validité des données reçues par une API REST.
Validation des entités JPA: S’assurer que les entités persistées respectent les contraintes de la base de données.
SLF4J(I): Connected with provider of type [ch.qos.logback.classic.spi.LogbackServiceProvider]
Fonctionnement général de Bean Validation
Où sont définies les contraintes ?
Directement sur les beans:
Annotations: Les contraintes sont définies sous forme d’annotations au niveau des attributs, méthodes ou classes.
Exemple:@NotNull, @Size, @Email
Dans des fichiers XML:
Pour des configurations plus complexes ou une séparation des contraintes.
Le rôle du validateur
Vérification des contraintes: Le validateur parcourt les annotations et vérifie si les valeurs des propriétés respectent les contraintes.
Génération des violations: En cas de non-respect d’une contrainte, le validateur génère une violation.
Contraintes personnalisées
Création d’annotations personnalisées: Pour définir des règles de validation spécifiques.
Implémentation d’un validateur associé: Pour vérifier la contrainte personnalisée.
%maven org.projectlombok:lombok:1.18.34
Field or Property level constraints
%%compile fr/univtln/bruno/Person.javapackage fr.univtln.bruno;importlombok.AllArgsConstructor;importjakarta.validation.constraints.*;@AllArgsConstructor(staticName ="of")publicclass Person {@NotNull(message ="Name cannot be null")privateString name;@AssertTrueprivateboolean active;@Size(min =18, max =100, message ="Description must be between {min} and {max} characters")privateString description;@Min(value =18, message ="Age should not be less than {value}")@Max(value =150, message ="Age should not be greater than {value}")privateint age;@Email(message ="Email '${validatedValue}' is not valid")privateString email;}
16:15:58.483 [IJava-executor-0] INFO o.h.validator.internal.util.Version -- HV000001: Hibernate Validator 8.0.1.Final
importfr.univtln.bruno.Person;Person p = Person.of("Pierre",true,"Sympa",25,"p@");Set<ConstraintViolation<Person>> constraintViolations = validator.validate(p);constraintViolations.stream().forEach(System.out::println);
Validation
ConstraintViolationImpl{interpolatedMessage='Email 'p@' is not valid', propertyPath=email, rootBeanClass=class fr.univtln.bruno.Person, messageTemplate='Email '${validatedValue}' is not valid'}
ConstraintViolationImpl{interpolatedMessage='Description must be between 18 and 100 characters', propertyPath=description, rootBeanClass=class fr.univtln.bruno.Person, messageTemplate='Description must be between {min} and {max} characters'}
Contraintes pré définies 1/2
@NotNull value is not null.
@AssertTrue value is true.
@Size size between the attributes min and max (String, Collection, Map, and Array).
@Min validates no smaller than the value attribute.
@Max validates no larger than the value attribute.
@Email validates valid email address.
@NotEmpty not null nor empty (String, Collection, Map or Array).
@NotBlank ot null or whitespace (Text only).
@Positive and @PositiveOrZero trictly positive, or positive including 0.
@Negative and @NegativeOrZero apply to numeric values and validate that they are strictly negative, or negative including 0 (numeric only).
@Past and @PastOrPresent date value is in the past or the past including the present.
@Future and @FutureOrPresent date value is in the future, or in the future including the present.
Contraintes Pré-définies (2/2)
Paramètres spécifiques: De nombreuses contraintes acceptent des paramètres pour affiner leur comportement.
Par exemple, @Size(min=2, max=10) pour spécifier une taille minimale et maximale.
Message personnalisé: L’attribut message permet de définir un message d’erreur personnalisé en cas de violation.
Interpolation: On peut utiliser des expressions entre accolades {} pour insérer des valeurs dynamiques dans le message.
Exemple : @Size(min=2, message="{javax.validation.constraints.Size.message}")
Incohérence entre contraintes: Bean Validation ne vérifie pas l’incohérence logique entre différentes contraintes.
Types incompatibles: Si une contrainte est appliquée à un type de donnée incompatible, une exception est levée.
[ConstraintViolationImpl{interpolatedMessage='must not be empty', propertyPath=maitre.name, rootBeanClass=class fr.univtln.bruno.Chien, messageTemplate='{jakarta.validation.constraints.NotEmpty.message}'}]
La Classe Validator
Le rôle du Validator
Moteur de validation: La classe Validator est responsable de l’exécution des règles de validation définies sur un objet.
Portée de la validation: La validation peut porter sur l’ensemble d’un objet ou sur une propriété spécifique.
Invocation du Validator
Invocation manuelle: Vous pouvez instancier un Validator et l’utiliser pour valider un objet à tout moment.
Invocation automatique: De nombreux frameworks (JPA, JAX-RS, etc.) intègrent Bean Validation et appellent automatiquement le Validator dans des contextes spécifiques (par exemple, avant de persister une entité JPA).
Exploration des contraintes
Récupération des violations: Le Validator retourne un ensemble de violations en cas d’échec de la validation.
Analyse des erreurs: Ces violations contiennent des informations détaillées sur les contraintes violées et les propriétés concernées.
x {fr.univtln.bruno.beanvalidation.CheckCase.message}
y {fr.univtln.bruno.beanvalidation.CheckCase.message}
Contrainte de classe
Une contrainte de classe s’applique à l’ensemble d’un objet et vérifie qu’il respecte certaines règles globales.
Définition: Une règle qui doit être vérifiée sur tous les attributs ou relations d’une classe.
Exemples:
Cohérence des données: Vérifier que la date de naissance est antérieure à la date d’embauche.
Règles métier: S’assurer qu’un client a au moins une commande associée.
Intégrité référentielle: Vérifier que les clés étrangères pointent vers des enregistrements existants.
%%compile fr/univtln/bruno/PersonDTO.javapackage fr.univtln.bruno;//Les personnes dont l'ID est entre 1 et 100 doivent avoir //au moins un name ou un nickname@PersonValidationpublicrecordPersonDTO(int id,String name,String nickname){};
/tmp/jupyterJava/fr/univtln/bruno/PersonDTO.java:4: error: cannot find symbol
@PersonValidation
^
symbol: class PersonValidation
1 error
Des contraintes peuvent être regroupées pour former des ensembles de règles à appliquer simultanément.
Définition: Un groupe de contraintes qui doivent toutes être satisfaites pour que la validation soit considérée comme réussie.
Exemples:
Validation de formulaire: Un formulaire de contact peut avoir un groupe de contraintes pour vérifier la validité de l’adresse email, du nom et du message.
Règles métier: Une commande peut avoir un groupe de contraintes pour vérifier la disponibilité des produits, le montant minimum de la commande et la validité de l’adresse de livraison.
%%compile fr/univtln/bruno/SimpleUser.javapackage fr.univtln.bruno;importlombok.*;importjakarta.validation.constraints.*;importjakarta.validation.constraints.Pattern;@Data(staticConstructor="of")publicclass SimpleUser {@NotNull@Pattern(regexp ="[a-zA-Z0-9]+", message ="only digits and letters")@Size(min =6, max =10, message ="must have between {min} and {max} characters")privatefinalString login;@NotNull@Pattern(regexp ="[a-zA-Z0-9]+", message ="only digits and letters")@Size(min =6, max =10, message ="must have between {min} and {max} characters")privatefinalString nickname;}
SimpleUser user = SimpleUser.of("Dave","rick");validator.validate(user).stream().map(v->"%s %s".formatted(v.getPropertyPath(), v.getMessage())).forEach(System.out::println);
login must have between 6 and 10 characters
nickname must have between 6 and 10 characters
%%compile fr/univtln/bruno/ValidIdentifier.javapackage fr.univtln.bruno;importjakarta.validation.Constraint;importjakarta.validation.ReportAsSingleViolation;importjakarta.validation.constraints.*;importjava.lang.annotation.*;importjakarta.validation.Payload;importstatic java.lang.annotation.ElementType.*;importstatic java.lang.annotation.RetentionPolicy.RUNTIME;@NotNull@Pattern(regexp =".*\\d.*", message ="must contain at least one numeric character")@Size(min =6, max =32, message ="must have between 6 and 32 characters")@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })@Retention(RUNTIME)@Documented@Constraint(validatedBy ={})@ReportAsSingleViolationpublic@interface ValidIdentifier {Stringmessage()default"field should have a valid length and contain numeric character(s).";Class<?>[]groups()default{};Class<?extends Payload>[]payload()default{};}