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


92

モデルの検証にJPA2.0 / Hibernate検証を使用しています。現在、2つのフィールドの組み合わせを検証する必要がある状況があります。

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

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

JPA 2.0 / Hibernateでこの種の検証を実行するにはどうすればよいですか?単純な@NotNullアノテーションでは、検証に合格するには、両方のゲッターがnull以外である必要があります。


回答:


102

複数のプロパティの検証には、クラスレベルの制約を使用する必要があります。 ビーン検証スニークピーク部分II:カスタム制約

###クラスレベルの制約

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

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

BeanValidation仕様によって提供されるソリューションは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ます。オブジェクトインスタンスにアクセスすることにより、クラスレベルの制約には多くの柔軟性があり、複数の相関プロパティを検証できます。順序付けはここでは方程式から除外されていることに注意してください。次の投稿でそれに戻ります。

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


17
この例では、インターフェースConstraintValidatorとアノテーション@Constraintが反転されています。そして、valid()は2つのパラメーターを取ります。
ギヨームフスタ2014

1
TYPEおよびRUNTIMEは、それぞれElementType.TYPEおよびRetentionPolicy.RUNTIMEに置き換える必要があります。
mark.monteiro 2016

2
@ mark.monteiro静的インポートを使用できます:import static java.lang.annotation.ElementType.*;およびimport static java.lang.annotation.RetentionPolicy.*;
cassiomolin 2017年

2
BeanValidationで動作するように例を書き直しました。見ていこちらを
カシオモリン2017年

1
この回答の下でCassioが言及したようなメッセージ、グループ、およびペイロードが存在する必要があるため、注釈のパラメーターは仕様の範囲内ではありません。
ピーター・S.

38

Bean Validationを適切に機能させるために、PascalThiventの回答で提供されている例を次のように書き直すことができます。

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

CDI BeanのWebSphereRESTfulプロジェクトでカスタムバリデーターをブートストラップまたは呼び出す方法は?私はすべてを書かれているが、カスタムの制約が働いていないか、または呼び出された
BalaajiChander

私は同様の検証で立ち往生していますが、私isoA2CodeはDBCountryテーブルに保存されています。ここからDB呼び出しを行うのは良い考えですか?また、と私はエントリにテーブルの外部キーを持たせたいのでAddress belongs_to、検証後にそれらをリンクしCountryたいと思います。対処する国をどのようにリンクしますか?addresscountry
krozaine

間違ったオブジェクトに型検証アノテーションを設定すると、BeanValidationフレームワークによって例外がスローされることに注意してください。たとえば@ValidAddress、Countryオブジェクトに注釈を設定すると、No validator could be found for constraint 'com.example.validation.ValidAddress' validating type 'com.example.Country'例外が発生します。
Jacob van Lingen

12

Bean Validation仕様を維持したい場合は、カスタムクラスレベルバリデーターが最適です。例は次のとおりです。

Hibernate Validator機能を使用することに満足している場合は、Validator-4.1.0.Final以降に提供されている@ScriptAssertを使用できます。JavaDocからの抜粋:

スクリプト式は、JSR 223(「JavaTMプラットフォーム用のスクリプト」)互換エンジンがクラスパスにある任意のスクリプト言語または式言語で記述できます。

例:

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

はい。Java6にはRhino(JavaScriptエンジン)が含まれているため、依存関係を追加せずにJavaScriptを式言語として使用できます。

3
これは、Hibernate Validator 5.1.1.Final
Ivan Hristov
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.