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

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 08
2
@ mark.monteiro Вы можете использовать статический импорт: import static java.lang.annotation.ElementType.*;иimport static java.lang.annotation.RetentionPolicy.*;
кассиомолин
2
Я переписал пример для работы с Bean Validation. Взгляните сюда .
кассиомолин 02
1
Параметры аннотации не соответствуют спецификации, потому что должно быть сообщение, группы и полезная нагрузка, как было упомянуто Cassio в этом ответе.
Питер С.
38

Чтобы правильно работать с 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;
        }
    }
}
кассиомолин
источник
Как запустить или вызвать настраиваемый валидатор в проекте WebSphere restful для компонента CDI? Я написал все, но настраиваемое ограничение не работает или не вызывается
BalaajiChander
Я застрял с аналогичной проверкой, но мой isoA2Codeхранится в Countryтаблице БД . Это хорошая идея сделать отсюда вызов БД? Кроме того, я хотел бы связать их после проверки, потому что Address belongs_toa 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 Validation, например здесь .

Если вы счастливы использовать функцию Hibernate Validator, вы можете использовать @ScriptAssert , который предоставляется с Validator-4.1.0.Final. Exceprt из своего 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. Final
Иван Христов