Как я могу проверить два или более полей в комбинации?

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

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

Модель недействительна, если оба getValue1() и getValue2() являются null и действительны в противном случае.

Как я могу выполнить такую ​​проверку с помощью JPA 2.0/Hibernate? С простой аннотацией @NotNull оба геттера должны быть не пустыми, чтобы пройти проверку.

Ответ 1

Для проверки нескольких свойств вы должны использовать ограничения на уровне класса. Из Проверка подлинности bean-компонента Sneak Peek, часть II: пользовательские ограничения:

Ограничения на уровне класса

Некоторые из вас выразили обеспокоенность о способности применять ограничение, охватывающее несколько свойства, или чтобы выразить ограничение которые зависят от нескольких свойств. Классическим примером является адрес Проверка. Адреса запутанные Правила:

  • название улицы несколько стандартно и обязательно должно иметь ограничение по длине
  • структура почтового индекса полностью зависит от страны
  • город часто можно соотнести с почтовым индексом, а некоторые проверки ошибок могут быть сделано (при условии, что проверка сервис доступен)
  • из-за этих взаимозависимостей простое ограничение уровня свойств делает отвечать всем требованиям

Решение, предлагаемое Бином Спецификация проверки состоит из двух частей:

  • он предлагает возможность принудительного применения набора ограничений до другой набор ограничений через использование групп и групповых последовательностей. Эта тема будет рассмотрена в следующая запись в блоге
  • позволяет определять ограничения на уровне класса

Ограничения на уровне класса являются регулярными ограничения (аннотация/ Дуэт реализации), которые применяются на класс, а не собственность. Сказал иными словами, ограничения на уровне класса получить экземпляр объекта (скорее чем значение свойства) в 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. По доступ к экземпляру объекта, класс ограничения уровня имеют много гибкость и может подтвердить несколько коррелированные свойства. Обратите внимание, что порядок не входит в уравнение здесь мы вернемся к этому в следующий пост.

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

Ответ 2

Для правильной работы с Bean Validation пример, приведенный в ответе Pascal Thivent, можно переписать следующим образом:

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

Ответ 3

Пользовательский валидатор уровня класса - это то, что нужно, если вы хотите остаться со спецификацией Bean Validation, например здесь.

Если вы счастливы использовать функцию 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;
}