Come posso convalidare due o più campi in combinazione?
-
03-10-2019 - |
Domanda
sto usando JPA 2.0 validazione / Hibernate per convalidare i miei modelli. Ora ho una situazione in cui la combinazione di due campi deve essere convalidato:
public class MyModel {
public Integer getValue1() {
//...
}
public String getValue2() {
//...
}
}
Il modello è non valida se entrambi getValue1()
e getValue2()
sono null
e valido altrimenti.
Come posso eseguire questo tipo di convalida con JPA 2.0 / Hibernate? Con una semplice annotazione @NotNull
entrambi getter devono essere non nullo per passare convalida.
Soluzione
Per più proprietà di convalida, è necessario utilizzare i vincoli a livello di classe. A partire dal Bean Validation Sneak Peek parte II: custom vincoli :
vincoli a livello di classe
Alcuni di voi hanno espresso preoccupazioni circa la capacità di applicare una vincolo di Multiple Spanning proprietà, o per vincolo espresso che dipendono diverse proprietà. L'esempio classico è l'indirizzo convalida. Indirizzi hanno intricato regole:
- un nome della via è in qualche modo standard e deve certamente avere un limite di lunghezza
- la struttura del codice di avviamento postale dipende interamente il paese
- la città può spesso essere correlata a un codice postale e un po 'il controllo degli errori lattina essere fatto (a condizione che una validazione servizio è accessibile)
- a causa di queste interdipendenze un vincolo semplice livello di proprietà fa per adattarsi al disegno di legge
La soluzione offerta dal Bean specifica convalida è duplice:
- che offre la possibilità di forzare un insieme di vincoli da applicare prima di un altra serie di vincoli attraverso la utilizzare dei gruppi e sequenze di gruppo. Questo argomento sarà trattato nel prossimo blog
- permette di definire i vincoli livello di classe
vincoli livello di classe sono regolari vincoli (annotazioni / attuazione duo) applicabili su classe piuttosto che una proprietà. Disse diversamente, i vincoli a livello di classe ricevere l'istanza di oggetto (piuttosto che il valore della proprietà) in
isValid
.@AddressAnnotation public class Address { @NotNull @Max(50) private String street1; @Max(50) private String street2; @Max(10) @NotNull private String zipCode; @Max(20) @NotNull String city; @NotNull private Country country; ... } @Constraint(validatedBy = MultiCountryAddressValidator.class) @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AddressAnnotation { String message() default "{error.address}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; } public class MultiCountryAddressValidator implements ConstraintValidator<AddressAnnotation, Address> { public void initialize(AddressAnnotation constraintAnnotation) { // initialize the zipcode/city/country correlation service } /** * Validate zipcode and city depending on the country */ public boolean isValid(Address object, ConstraintValidatorContext context) { 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; } // ... } }
Le regole di convalida degli indirizzi avanzata sono stati lasciati fuori dell'indirizzo oggetto e realizzato da
MultiCountryAddressValidator
. Di Accedendo l'istanza dell'oggetto, classe vincoli di livello hanno un sacco di flessibilità e in grado di convalidare multipla Proprietà correlate. Nota che ordinamento viene lasciato fuori dall'equazione qui, torneremo ad esso in prossimo post.Il gruppo di esperti ha discusso varie più proprietà supporto approcci: pensiamo che il livello di classe approccio vincolo fornisce sia abbastanza semplicità e flessibilità rispetto ad altri livello di proprietà approcci che coinvolgono le dipendenze. Il tuo feedback è benvenuto.
Altri suggerimenti
Per funzionare correttamente con Bean Validation , l'esempio fornito in Pascal Thivent di rispondere potrebbe essere riscritto come segue:
@ValidAddress
public class Address {
@NotNull
@Size(max = 50)
private String street1;
@Size(max = 50)
private String street2;
@NotNull
@Size(max = 10)
private String zipCode;
@NotNull
@Size(max = 20)
private String city;
@Valid
@NotNull
private Country country;
// Getters and setters
}
public class Country {
@NotNull
@Size(min = 2, max = 2)
private String iso2;
// Getters and setters
}
@Documented
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = { MultiCountryAddressValidator.class })
public @interface ValidAddress {
String message() default "{com.example.validation.ValidAddress.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class MultiCountryAddressValidator
implements ConstraintValidator<ValidAddress, Address> {
public void initialize(ValidAddress 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 validatore livello di classe personalizzato è la strada da percorrere, se si vuole stare con la specifica Bean Validation, ad esempio qui .
Se siete felici di utilizzare una funzione Hibernate Validator, è possibile utilizzare @ ScriptAssert , che viene fornito dal Validator-4.1.0.Final. Exceprt dalla sua JavaDoc:
espressioni script può essere scritto in qualsiasi script o espressione lingua, per la quale un JSR 223 ( "Scripting per la piattaforma JavaTM") il motore compatibile può essere trovato nel classpath.
Esempio:
@ScriptAssert(lang = "javascript", script = "_this.value1 != null || _this != value2)")
public class MyBean {
private String value1;
private String value2;
}