【问题标题】:How can I validate two or more fields in combination?如何组合验证两个或多个字段?
【发布时间】:2011-02-16 10:17:20
【问题描述】:

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

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

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

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

【问题讨论】:

标签: java jpa jpa-2.0 bean-validation


【解决方案1】:

您可以像这样使用@AssertTrue 验证:

public class MyModel {
    
    // values

    @AssertTrue(message = "Values are invalid")
    private String isValid() {
      return value1 != null || value2 != null;
    }
}

【讨论】:

  • 感谢您的解决方案。最初我想使用@ScriptAssert,但有些 bean 有许多需要验证的规则,所以这更直观:每个规则一个验证方法。
【解决方案2】:

对于多个属性验证,您应该使用类级别的约束。从 Bean Validation Sneak Peek part II: custom constraints:

类级约束

你们中的一些人表达了担忧 关于申请的能力 跨越多个的约束 属性,或表达约束 这取决于几个属性。 经典的例子是地址 验证。地址错综复杂 规则:

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

Bean 提供的解决方案 验证规范有两个方面:

  • 它提供了在执行之前强制应用一组约束的能力 其他一组约束通过 使用组和组序列。 该主题将在 下一篇博文
  • 它允许定义类级别的约束

类级约束是常规的 约束(注释/ 实施二重奏),适用于 类而不是属性。说 不同的是,类级别的约束 接收对象实例(而不是 比属性值)在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("@AddressAnnotation only applies to Address objects");
        }
        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。经过 访问对象实例,类 水平约束有很多 灵活性,可以验证多个 相关属性。注意 排序被排除在等式之外 在这里,我们将在 下一篇文章。

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

【讨论】:

  • 接口ConstraintValidator和注解@Constraint在例子中已经倒置了。并且是 valid() 需要 2 个参数。
  • TYPERUNTIME 应分别替换为 ElementType.TYPERetentionPolicy.RUNTIME
  • @mark.monteiro 您可以使用静态导入:import static java.lang.annotation.ElementType.*;import static java.lang.annotation.RetentionPolicy.*;
  • 我已经重写了示例以使用 Bean Validation。看看here
  • 注释的参数不在规范范围内,因为必须有一条消息、组和有效负载,就像 Cassio 在这个答案下提到的那样。
【解决方案3】:

编程语言:Java

这是一个对我有帮助的解决方案。

要求:

  1. 在 UI 上有一个包含对象列表的表,该表正在映射到具有 fk 关系的多个表/对象。

  2. 现在验证来自多个 fks,只有 3 列不能重复。我的意思是3的组合不能重复。

注意:当我在 Java 上开发自定义框架时,没有使用 HashCode 或 equals 的选项。如果我将使用数组索引迭代会增加我不想要的时间复杂度。

解决方案:

我准备了一个 String ,它是一个自定义 String ,其中包含 FK1 的 ID#FK2 的 ID#FK3 的 ID 例如:字符串将形成 -> 1000L#3000L#1300L#

这个字符串,我们将使用 set 的 add() 添加到一个 set 中,如果出现重复则返回 false。

基于这个标志,我们可以抛出验证消息。

这对我有帮助。 DS 可能无法提供一些方案和限制。

【讨论】:

    【解决方案4】:

    如果您想保持 Bean 验证规范,例如 here,则可以使用自定义类级别验证器。

    如果您乐于使用 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 作为表达式语言而无需添加额外的依赖项。
    • Here 是如何使用 Hibernate Validator 5.1.1.Final 进行此类验证的示例
    【解决方案5】:

    为了与Bean Validation 正常工作,Pascal Thivent 的answer 中提供的示例可以重写如下:

    @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 bean 引导或调用自定义验证器?我已经写了所有,但自定义约束不起作用或调用
    • 我遇到了类似的验证,但我的 isoA2Code 存储在 DB Country 表中。从这里拨打数据库电话是个好主意吗?另外,我想在验证后链接它们,因为Address belongs_to 一个Country 并且我希望address 条目具有country 表的外键。我如何将国家/地区与地址联系起来?
    • 请注意,当您在错误的对象上设置类型验证注解时,Bean Validation 框架将抛出​​异常。例如,如果您在 Country 对象中设置 @ValidAddress 注释,您将获得 No validator could be found for constraint 'com.example.validation.ValidAddress' validating type 'com.example.Country' 异常。
    猜你喜欢
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 2019-01-04
    • 1970-01-01
    • 1970-01-01
    • 2014-04-10
    相关资源
    最近更新 更多