Comment puis-je valider plusieurs champs en même temps?

J’utilise la validation JPA 2.0 / Hibernate pour valider mes modèles. J’ai maintenant une situation où la combinaison de deux champs doit être validée:

public class MyModel { public Integer getValue1() { //... } public Ssortingng getValue2() { //... } } 

Le modèle n’est pas valide si getValue1() et getValue2() sont null et valables autrement.

Comment puis-je effectuer ce type de validation avec JPA 2.0 / Hibernate? Avec une simple annotation @NotNull deux getters doivent être non NULL pour réussir la validation.

Pour la validation de plusieurs propriétés, vous devez utiliser des contraintes de niveau classe. De Bean Validation Sneak Peek partie II: contraintes personnalisées :

Contraintes au niveau de la classe

Certains d’entre vous ont exprimé des inquiétudes quant à la possibilité d’appliquer une contrainte couvrant plusieurs propriétés ou d’exprimer une contrainte qui dépend de plusieurs propriétés. L’exemple classique est la validation d’adresse. Les adresses ont des règles complexes:

  • un nom de rue est quelque peu standard et doit certainement avoir une limite de longueur
  • la structure du code postal dépend entièrement du pays
  • la ville peut souvent être associée à un code postal et une vérification des erreurs peut être effectuée (à condition qu’un service de validation soit accessible)
  • en raison de ces interdépendances, une contrainte de niveau de propriété simple fait pour adapter la facture

La solution proposée par la spécification Bean Validation est double:

  • Il offre la possibilité d’appliquer un ensemble de contraintes avant un autre ensemble de contraintes grâce à l’utilisation de groupes et de séquences de groupes. Ce sujet sera traité dans la prochaine entrée de blog
  • il permet de définir des contraintes au niveau de la classe

Les contraintes au niveau de la classe sont des contraintes régulières (annotation / duo d’implémentation) qui s’appliquent à une classe plutôt qu’à une propriété. Autrement dit, les contraintes de niveau classe reçoivent l’instance d’object (plutôt que la valeur de la propriété) dans isValid .

 @Address public class Address { @NotNull @Max(50) private Ssortingng street1; @Max(50) private Ssortingng street2; @Max(10) @NotNull private Ssortingng zipCode; @Max(20) @NotNull Ssortingng city; @NotNull private Country country; ... } @Constraint(validatedBy = MultiCountryAddressValidator.class) @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Address { Ssortingng message() default "{error.address}"; Ssortingng[] groups() default {}; } public class MultiCountryAddressValidator implements ConstraintValidator
{ public void initialize(Address constraintAnnotation) { // initialize the zipcode/city/country correlation service } /** * Validate zipcode and city depending on the country */ public boolean isValid(Object object) { if (!(object instanceof Address)) { throw new IllegalArgumentException("@Address only applies to Address"); } Address address = (Address) object; Country country = address.getCountry(); if (country.getISO2() == "FR") { // check address.getZipCode() structure for France (5 numbers) // check zipcode and city correlation (calling an external service?) return isValid; } else if (country.getISO2() == "GR") { // check address.getZipCode() structure for Greece // no zipcode / city correlation available at the moment return isValid; } // ... } }

Les règles de validation d’adresse avancées ont été MultiCountryAddressValidator de l’object d’adresse et implémentées par MultiCountryAddressValidator . En accédant à l’instance d’object, les contraintes de niveau de classe ont beaucoup de flexibilité et peuvent valider plusieurs propriétés corrélées. Notez que la commande est laissée en dehors de l’équation ici, nous y reviendrons dans le prochain post.

Le groupe d’experts a discuté de diverses approches de support de propriétés multiples: nous pensons que l’approche de la contrainte au niveau de la classe fournit à la fois suffisamment de simplicité et de flexibilité par rapport à d’autres approches de niveau propriété impliquant des dépendances. Vos commentaires sont les bienvenus.

Pour fonctionner correctement avec Bean Validation , l’exemple fourni dans la réponse de Pascal Thivent pourrait être réécrit comme suit:

 @ValidAddress public class Address { @NotNull @Size(max = 50) private Ssortingng street1; @Size(max = 50) private Ssortingng street2; @NotNull @Size(max = 10) private Ssortingng zipCode; @NotNull @Size(max = 20) private Ssortingng city; @Valid @NotNull private Country country; // Getters and setters } 
 public class Country { @NotNull @Size(min = 2, max = 2) private Ssortingng iso2; // Getters and setters } 
 @Documented @Target(TYPE) @Retention(RUNTIME) @Constraint(validatedBy = { MultiCountryAddressValidator.class }) public @interface ValidAddress { Ssortingng message() default "{com.example.validation.ValidAddress.message}"; Class[] groups() default {}; Class[] payload() default {}; } 
 public class MultiCountryAddressValidator implements ConstraintValidator { public void initialize(ValidateAddress constraintAnnotation) { } @Override public boolean isValid(Address address, ConstraintValidatorContext constraintValidatorContext) { Country country = address.getCountry(); if (country == null || country.getIso2() == null || address.getZipCode() == null) { return true; } switch (country.getIso2()) { case "FR": return // Check if address.getZipCode() is valid for France case "GR": return // Check if address.getZipCode() is valid for Greece default: return true; } } } 

Un validateur au niveau de la classe est la voie à suivre lorsque vous souhaitez conserver la spécification de validation du bean. Si vous êtes heureux d’utiliser une fonctionnalité de validateur Hibernate, vous pouvez utiliser @ScriptAssert , fourni dans Validator-4.1.0.Final.

Vous pouvez également utiliser la reflection. Voici l’article qui parle de l’implémentation en utilisant la reflection.

http://dolszewski.com/java/cross-field-validation/