Jak mogę zweryfikować połączenie dwóch lub więcej pól?


92

Używam weryfikacji JPA 2.0 / Hibernate do sprawdzania poprawności moich modeli. Mam teraz sytuację, w której połączenie dwóch pól musi zostać sprawdzone:

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

Model jest nieważne , gdy oba getValue1()i getValue2()są nulli ważne inaczej.

Jak mogę przeprowadzić tego rodzaju walidację za pomocą JPA 2.0 / Hibernate? W przypadku prostej @NotNulladnotacji oba metody pobierające muszą mieć wartość różną od null, aby przejść weryfikację.


Odpowiedzi:


102

W przypadku walidacji wielu właściwości należy użyć ograniczeń na poziomie klasy. Z Bean Validation Sneak Peek, część II: niestandardowe ograniczenia :

### Ograniczenia na poziomie klasy

Niektórzy z was wyrazili obawy co do możliwości zastosowania ograniczenia obejmującego wiele właściwości lub wyrażenia ograniczenia zależnego od kilku właściwości. Klasycznym przykładem jest walidacja adresu. Adresy mają skomplikowane zasady:

  • nazwa ulicy jest dość standardowa iz pewnością musi mieć ograniczenie długości
  • struktura kodu pocztowego całkowicie zależy od kraju
  • miasto często można skorelować z kodem pocztowym i można sprawdzić pewne błędy (pod warunkiem, że dostępna jest usługa walidacji)
  • ze względu na te współzależności proste ograniczenie na poziomie właściwości spełnia wymagania

Rozwiązanie oferowane przez specyfikację Bean Validation jest dwojakie:

  • oferuje możliwość wymuszenia zastosowania zestawu ograniczeń przed innym zestawem ograniczeń poprzez użycie grup i sekwencji grupowych. Temat ten zostanie omówiony w kolejnym wpisie na blogu
  • pozwala zdefiniować ograniczenia na poziomie klasy

Ograniczenia na poziomie klasy to zwykłe ograniczenia (duet adnotacji / implementacji), które mają zastosowanie do klasy, a nie do właściwości. Mówiąc inaczej, ograniczenia na poziomie klasy odbierają instancję obiektu (zamiast wartości właściwości) w formacie 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;
        }
        // ...
    }
}

Zaawansowane reguły sprawdzania poprawności adresu zostały pominięte w obiekcie adresu i zaimplementowane przez MultiCountryAddressValidator. Uzyskując dostęp do instancji obiektu, ograniczenia na poziomie klasy zapewniają dużą elastyczność i mogą weryfikować wiele skorelowanych właściwości. Zauważ, że porządkowanie nie jest tutaj uwzględnione, wrócimy do tego w następnym poście.

Grupa ekspertów omówiła różne podejścia do obsługi wielu właściwości: uważamy, że podejście z ograniczeniami na poziomie klasy zapewnia zarówno wystarczającą prostotę, jak i elastyczność w porównaniu z innymi podejściami na poziomie właściwości obejmującymi zależności. Twoja opinia jest mile widziana.


17
Interfejs ConstraintValidator i adnotacja @Constraint zostały w przykładzie odwrócone. I jest poprawne () przyjmuje 2 parametry.
— Guillaume Husta

1
TYPEi RUNTIMEpowinien być zastąpiony ElementType.TYPEi RetentionPolicy.RUNTIMEodpowiednio.
— mark.monteiro

2
@ mark.monteiro Możesz użyć importu statycznego: import static java.lang.annotation.ElementType.*;iimport static java.lang.annotation.RetentionPolicy.*;
— cassiomolin

2
Przepisałem przykład, aby działał z walidacją fasoli. Zajrzyj tutaj .
— kasiomolina

1
Parametry adnotacji nie mieszczą się w specyfikacji, ponieważ musi istnieć komunikat, grupy i ładunek, jak wspomniano w tej odpowiedzi Cassio.
— Peter S.

38

Aby pracować poprawnie z Bean Validation , na przykład przewidzianej w Pascalu Thivent za odpowiedź może zostać przepisany w następujący sposób:

@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;
        }
    }
}

Jak uruchomić lub wywołać niestandardowy moduł sprawdzania poprawności w projekcie WebSphere Restful dla komponentu bean CDI? Napisałem wszystko, ale niestandardowe ograniczenie nie działa lub nie zostało wywołane
— BalaajiChander

Utknąłem z podobną walidacją, ale moja isoA2Codejest przechowywana w Countrytabeli DB . Czy to dobry pomysł, aby wywołać DB stąd? Chciałbym również połączyć je po walidacji, ponieważ Address belongs_toa Countryi chcę, aby addresswpis zawierał countryklucz obcy tabeli. Jak powiązałbym kraj, z którym należy się zwrócić?
— krozaine

Zauważ, że jeśli ustawisz adnotację walidacji typu na niewłaściwym obiekcie, wyjątek zostanie zgłoszony przez platformę sprawdzania poprawności Bean. Na przykład, jeśli ustawisz @ValidAddressadnotację w obiekcie Country, otrzymasz No validator could be found for constraint 'com.example.validation.ValidAddress' validating type 'com.example.Country'wyjątek.
— Jacob van Lingen

12

Niestandardowy walidator na poziomie klasy jest dobrym rozwiązaniem, jeśli chcesz pozostać przy specyfikacji walidacji fasoli, na przykład tutaj .

Jeśli z przyjemnością korzystasz z funkcji Hibernate Validator, możesz użyć @ScriptAssert , który jest dostępny od wersji Validator-4.1.0.Final. Exceprt z JavaDoc:

Wyrażenia skryptowe można pisać w dowolnym języku skryptowym lub języku wyrażeń, dla którego w ścieżce klas można znaleźć silnik zgodny z JSR 223 („Scripting for the JavaTM Platform”).

Przykład:

@ScriptAssert(lang = "javascript", script = "_this.value1 != null || _this != value2)")
public class MyBean {
  private String value1;
  private String value2;
}

Tak, a Java 6 zawiera Rhino (silnik JavaScript), więc możesz używać JavaScript jako języka wyrażeń bez dodawania dodatkowych zależności.

3
Oto przykład, jak utworzyć taką walidację za pomocą Hibernate Validator 5.1.1.Final
— Ivan Hristov
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.