Як я можу перевірити два або більше полів у поєднанні?


92

Я використовую перевірку JPA 2.0 / Hibernate для перевірки своїх моделей. Зараз у мене ситуація, коли комбінація двох полів повинна бути перевірена:

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

Модель недійсна, якщо обидва getValue1()і getValue2()є, nullі дійсні інакше.

Як я можу виконати такий тип перевірки за допомогою JPA 2.0 / Hibernate? За допомогою простої @NotNullанотації обидва геттери повинні бути ненульовими, щоб пройти перевірку.


Відповіді:


102

Для перевірки кількох властивостей слід використовувати обмеження на рівні класу. З Bean Validation Sneak Peek, частина II: користувацькі обмеження :

### Обмеження на рівні класу

Деякі з вас висловлювали занепокоєння щодо можливості застосовувати обмеження, що охоплює кілька властивостей, або виражати обмеження, які залежать від кількох властивостей. Класичний приклад - перевірка адреси. Адреси мають складні правила:

  • назва вулиці є дещо стандартною і, безумовно, повинна мати обмеження довжини
  • структура поштового індексу повністю залежить від країни
  • місто часто можна співвіднести з поштовим індексом і зробити певну перевірку помилок (за умови, що доступна служба перевірки)
  • через ці взаємозалежності просте обмеження на рівні власності відповідає законопроекту

Рішення, запропоноване специфікацією Bean Validation, є подвійним:

  • він пропонує можливість змусити набір обмежень застосовуватися перед іншим набором обмежень за допомогою груп та послідовностей груп. Ця тема буде розглянута в наступному записі в блозі
  • це дозволяє визначити обмеження рівня класу

Обмеження рівня класу - це регулярні обмеження (дует анотацій / реалізації), які застосовуються до класу, а не до властивості. Інакше сказано, обмеження рівня класу отримують екземпляр об'єкта (а не значення властивості) у 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;
        }
        // ...
    }
}

Розширені правила перевірки адрес залишились поза об'єктом адреси та реалізовані MultiCountryAddressValidator. Отримуючи доступ до екземпляра об’єкта, обмеження рівня класу мають велику гнучкість і можуть перевірити кілька корельованих властивостей. Зверніть увагу, що впорядкування тут не подається з рівняння, ми повернемось до нього у наступному дописі.

Експертна група обговорила різні підходи до підтримки властивостей: ми вважаємо, що підхід обмеження на рівні класу забезпечує достатню простоту та гнучкість у порівнянні з іншими підходами на рівні властивостей, що включають залежності. Ваш відгук вітається.


17
Інтерфейс ConstraintValidator та анотація @Constraint були перетворені у прикладі. І valid () приймає 2 параметри.
Гійом Хуста

1
TYPEі RUNTIMEслід замінити на ElementType.TYPEта RetentionPolicy.RUNTIMEвідповідно.
mark.monteiro

2
@ mark.monteiro Ви можете використовувати статичне імпортування: import static java.lang.annotation.ElementType.*;іimport static java.lang.annotation.RetentionPolicy.*;
cassiomolin 02

2
Я переписав приклад для роботи з Bean Validation. Подивіться тут .
cassiomolin

1
Параметри анотації не відповідають правильним специфікаціям, оскільки має бути повідомлення, групи та корисне навантаження, як це було згадано Кассіо під цією відповіддю.
Пітер С.

38

Для належної роботи з перевіркою Bean приклад, наведений у відповіді Паскаля Тівента, можна переписати таким чином:

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

Як завантажити або викликати власний валідатор у спокійному проекті WebSphere для компонента CDI? Я написав, що все, крім спеціального обмеження, не працює або не викликається
BalaajiChander

У мене застрягла подібна перевірка, але моя isoA2Codeзберігається в Countryтаблиці БД . Чи хороша ідея зробити дзвінок з БД звідси? Крім того, я хотів би зв’язати їх після перевірки, оскільки Address belongs_toа, Countryі я хочу, щоб addressзапис мав countryзовнішній ключ таблиці. Як я можу зв’язати країну з адресою?
krozaine

Зверніть увагу, що коли ви встановлюєте анотацію перевірки типу на неправильний об'єкт, фреймворк Bean Validation видає виняток. Наприклад, якщо ви встановите @ValidAddressанотацію для об'єкта Country, ви отримаєте No validator could be found for constraint 'com.example.validation.ValidAddress' validating type 'com.example.Country'виняток.
Якоб ван Лінген,

12

Спеціальний валідатор рівня класу - це шлях, коли ви хочете дотримуватися специфікації перевірки Bean, приклад тут .

Якщо ви із задоволенням користуєтесь функцією Hibernate Validator, ви можете використовувати @ScriptAssert , яка надається з Validator-4.1.0.Final. Крім його JavaDoc:

Вирази сценарію можна писати будь-якою мовою сценаріїв чи виразів, для яких на шляху до класу можна знайти механізм, сумісний з JSR 223 ("Сценарії для платформи JavaTM").

Приклад:

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

Так, і Java 6 включає Rhino (механізм JavaScript), тому ви можете використовувати JavaScript як мову виразів, не додаючи зайвих залежностей.

3
Ось приклад того, як створити таку перевірку за допомогою Hibernate Validator 5.1.1. Фінал
Іван Христов,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.