Java 如何组合验证两个或多个字段?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2781771/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 12:48:15  来源:igfitidea点击:

How can I validate two or more fields in combination?

javajpajpa-2.0bean-validation

提问by Daniel Rikowski

I'm using JPA 2.0/Hibernate validation to validate my models. I now have a situation where the combination of two fields has to be validated:

我正在使用 JPA 2.0/Hibernate 验证来验证我的模型。我现在有一种情况,必须验证两个字段的组合:

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

The model is invalidif both getValue1()and getValue2()are nulland valid otherwise.

如果和都有效,则模型无效,否则有效。getValue1()getValue2()null

How can I perform this kind of validation with JPA 2.0/Hibernate? With a simple @NotNullannotation both getters must be non-null to pass validation.

如何使用 JPA 2.0/Hibernate 执行这种验证?使用简单的@NotNull注释,两个 getter 都必须非空才能通过验证。

采纳答案by Pascal Thivent

For multiple properties validation, you should use class-level constraints. From Bean Validation Sneak Peek part II: custom constraints:

对于多个属性验证,您应该使用类级约束。来自 Bean Validation Sneak Peek 第二部分:自定义约束

Class-level constraints

Some of you have expressed concerns about the ability to apply a constraint spanning multiple properties, or to express constraint which depend on several properties. The classical example is address validation. Addresses have intricate rules:

  • a street name is somewhat standard and must certainly have a length limit
  • the zip code structure entirely depends on the country
  • the city can often be correlated to a zipcode and some error checking can be done (provided that a validation service is accessible)
  • because of these interdependencies a simple property level constraint does to fit the bill

The solution offered by the Bean Validation specification is two-fold:

  • it offers the ability to force a set of constraints to be applied before an other set of constraints through the use of groups and group sequences. This subject will be covered in the next blog entry
  • it allows to define class level constraints

Class level constraints are regular constraints (annotation / implementation duo) which apply on a class rather than a property. Said differently, class-level constraints receive the object instance (rather than the property value) 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;
        }
        // ...
    }
}

The advanced address validation rules have been left out of the address object and implemented by MultiCountryAddressValidator. By accessing the object instance, class level constraints have a lot of flexibility and can validate multiple correlated properties. Note that ordering is left out of the equation here, we will come back to it in the next post.

The expert group has discussed various multiple properties support approaches: we think the class level constraint approach provides both enough simplicity and flexibility compared to other property level approaches involving dependencies. Your feedback is welcome.

班级约束

你们中的一些人对应用跨多个属性的约束或表达依赖于多个属性的约束的能力表示担忧。典型的例子是地址验证。地址有复杂的规则:

  • 街道名称有点标准,当然必须有长度限制
  • 邮政编码结构完全取决于国家
  • 城市通常可以与邮政编码相关联,并且可以进行一些错误检查(前提是可以访问验证服务)
  • 由于这些相互依赖性,一个简单的财产级别限制确实符合要求

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。通过访问对象实例,类级别的约束具有很大的灵活性,并且可以验证多个相关的属性。请注意,这里没有考虑排序,我们将在下一篇文章中重新讨论。

专家组讨论了各种多属性支持方法:我们认为类级别约束方法与其他涉及依赖项的属性级别方法相比提供了足够的简单性和灵活性。欢迎您的反馈。

回答by Hardy

A custom class level validator is the way to go, when you want to stay with the Bean Validation specification, example here.

当您想使用 Bean 验证规范时,自定义类级别验证器是一种可行的方法,示例here

If you are happy to use a Hibernate Validator feature, you could use @ScriptAssert, which is provided since Validator-4.1.0.Final. Exceprt from its JavaDoc:

如果您乐于使用 Hibernate Validator 功能,您可以使用@ScriptAssert,它自 Validator-4.1.0.Final 起提供。摘自其 JavaDoc:

Script expressions can be written in any scripting or expression language, for which a JSR 223("Scripting for the JavaTM Platform") compatible engine can be found on the classpath.

脚本表达式可以用任何脚本或表达式语言编写,在类路径上可以找到与JSR 223(“JavaTM 平台脚本”)兼容的引擎。

Example:

例子:

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

回答by cassiomolin

To work properly with Bean Validation, the example provided in Pascal Thivent's answercould be rewritten as follows:

为了与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;
        }
    }
}