web-dev-qa-db-ja.com

2つ以上のフィールドを組み合わせて検証するにはどうすればよいですか?

モデルの検証にJPA 2.0/Hibernate検証を使用しています。 2つのフィールドの組み合わせを検証する必要がある状況になりました。

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

モデルは、getValue1()getValue2()の両方がnullである場合はinvalidであり、そうでない場合は有効です。

JPA 2.0/Hibernateでこの種の検証を実行するにはどうすればよいですか?単純な_@NotNull_注釈を使用すると、検証に合格するには両方のゲッターがnull以外である必要があります。

70
Daniel Rikowski

複数のプロパティを検証するには、クラスレベルの制約を使用する必要があります。 From Bean Validation Sneak PeekパートII:カスタム制約

クラスレベルの制約

複数のプロパティにまたがる制約を適用したり、複数のプロパティに依存する制約を表現したりする機能について懸念を表明している人もいます。古典的な例は、アドレス検証です。アドレスには複雑なルールがあります。

  • 通りの名前は多少標準的であり、長さの制限が必ず必要です
  • 郵便番号の構造は完全に国に依存します
  • 多くの場合、都市は郵便番号に関連付けられ、エラーチェックを実行できます(検証サービスにアクセスできる場合)
  • これらの相互依存関係のため、単純なプロパティレベルの制約が法案に適合します

Bean Validation仕様で提供されるソリューションは2つあります。

  • グループおよびグループシーケンスを使用して、他の制約セットよりも前に制約セットを強制的に適用する機能を提供します。このテーマは、次のブログエントリで取り上げます。
  • クラスレベルの制約を定義できます。

クラスレベルの制約は、プロパティではなくクラスに適用される通常の制約(注釈/実装デュオ)です。別の言い方をすれば、クラスレベルの制約は、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によって実装されています。オブジェクトインスタンスにアクセスすることにより、クラスレベルの制約は非常に柔軟になり、複数の相関プロパティを検証できます。ここでの順序付けは方程式から除外されていることに注意してください。次の投稿でそれに戻ります。

専門家グループは、さまざまな複数のプロパティサポートアプローチについて説明しました。クラスレベルの制約アプローチは、依存関係を含む他のプロパティレベルのアプローチと比較して、十分な単純さと柔軟性の両方を提供すると考えます。あなたのフィードバックは大歓迎です。

83
Pascal Thivent

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;
        }
    }
}
25
cassiomolin

Bean Validation仕様を維持したい場合は、クラスレベルのバリデーターを使用します。 Hibernate Validator機能を使用することに満足している場合は、@ ScriptAssertを使用できます。これはValidator-4.1.0.Finalで提供されます。

8
Hardy