Bagaimana saya bisa memvalidasi dua atau lebih bidang dalam kombinasi?

92

Saya menggunakan validasi JPA 2.0 / Hibernate untuk memvalidasi model saya. Saya sekarang memiliki situasi di mana kombinasi dua bidang harus divalidasi:

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

Model ini tidak valid jika kedua getValue1()dan getValue2()yang nulldan berlaku sebaliknya.

Bagaimana saya bisa melakukan validasi semacam ini dengan JPA 2.0 / Hibernate? Dengan @NotNullanotasi sederhana, kedua pengambil harus bukan null untuk lolos validasi.

Daniel Rikowski
sumber
2
kemungkinan duplikat validasi Cross field dengan Hibernate Validator (JSR 303)
Steve Chambers

Jawaban:

102

Untuk validasi beberapa properti, Anda harus menggunakan batasan tingkat kelas. Dari Validasi Bean Sneak Peek bagian II: batasan khusus :

### Batasan tingkat kelas

Beberapa dari Anda telah mengungkapkan kekhawatiran tentang kemampuan untuk menerapkan batasan yang mencakup beberapa properti, atau untuk mengekspresikan batasan yang bergantung pada beberapa properti. Contoh klasiknya adalah validasi alamat. Alamat memiliki aturan yang rumit:

  • nama jalan agak standar dan tentunya harus memiliki batas panjang
  • struktur kode pos sepenuhnya tergantung pada negaranya
  • kota sering kali dapat dihubungkan dengan kode pos dan beberapa pemeriksaan kesalahan dapat dilakukan (asalkan layanan validasi dapat diakses)
  • karena saling ketergantungan ini kendala tingkat properti sederhana dilakukan agar sesuai dengan tagihan

Solusi yang ditawarkan oleh spesifikasi Validasi Bean ada dua:

  • ia menawarkan kemampuan untuk memaksa sekumpulan kendala untuk diterapkan sebelum rangkaian kendala lainnya melalui penggunaan kelompok dan urutan kelompok. Subjek ini akan dibahas di entri blog berikutnya
  • itu memungkinkan untuk mendefinisikan batasan tingkat kelas

Batasan tingkat kelas adalah batasan reguler (anotasi / implementasi duo) yang berlaku pada kelas daripada properti. Dengan kata lain, batasan tingkat kelas menerima instance objek (bukan nilai properti) di 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;
        }
        // ...
    }
}

Aturan validasi alamat lanjutan telah ditinggalkan dari objek alamat dan diterapkan oleh MultiCountryAddressValidator. Dengan mengakses instance objek, batasan tingkat kelas memiliki banyak fleksibilitas dan dapat memvalidasi beberapa properti yang berkorelasi. Perhatikan bahwa urutan ditinggalkan dari persamaan di sini, kita akan kembali ke pos berikutnya.

Kelompok ahli telah membahas berbagai pendekatan dukungan beberapa properti: menurut kami pendekatan batasan tingkat kelas memberikan kesederhanaan dan fleksibilitas yang cukup dibandingkan dengan pendekatan tingkat properti lain yang melibatkan ketergantungan. Tanggapan Anda diterima.

Pascal Thivent
sumber
17
Antarmuka ConstraintValidator dan anotasi @Constraint telah dibalik dalam contoh. Dan valid () membutuhkan 2 parameter.
Guillaume Husta
1
TYPEdan RUNTIMEharus diganti dengan ElementType.TYPEdan RetentionPolicy.RUNTIME, masing-masing.
tandai Monteiro
2
@ mark.monteiro Anda dapat menggunakan impor statis: import static java.lang.annotation.ElementType.*;danimport static java.lang.annotation.RetentionPolicy.*;
cassiomolin
2
Saya telah menulis ulang contoh untuk bekerja dengan Validasi Bean. Lihat di sini .
cassiomolin
1
Parameter anotasi tidak sesuai spesifikasi yang benar, karena harus ada pesan, grup, dan muatan seperti yang disebutkan oleh Cassio di bawah jawaban ini.
Peter S.
38

Untuk bekerja dengan baik dengan Validasi Bean , contoh yang diberikan dalam jawaban Pascal Thivent dapat ditulis ulang sebagai berikut:

@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;
        }
    }
}
cassiomolin.dll
sumber
Bagaimana cara melakukan bootstrap atau memanggil validator kustom dalam proyek WebSphere yang tenang untuk kacang CDI? Saya telah menulis semua kecuali batasan khusus tidak berfungsi atau dipanggil
BalaajiChander
Saya terjebak dengan validasi serupa, tetapi saya isoA2Codedisimpan dalam Countrytabel DB . Apakah ini ide yang bagus untuk menelepon DB dari sini? Juga, saya ingin menautkan mereka setelah validasi karena Address belongs_toa Countrydan saya ingin addressentri memiliki countrykunci asing tabel. Bagaimana saya menghubungkan negara ke alamat?
krozaine
Perhatikan bahwa ketika Anda menyetel anotasi validasi tipe pada objek yang salah, pengecualian akan dilemparkan oleh kerangka Validasi Bean. Misalnya jika Anda menyetel @ValidAddressanotasi di objek Negara, Anda akan mendapatkan No validator could be found for constraint 'com.example.validation.ValidAddress' validating type 'com.example.Country'pengecualian.
Jacob van Lingen
12

Sebuah validator tingkat kelas kustom adalah cara untuk pergi, ketika Anda ingin tetap dengan spesifikasi Validasi Bean, misalnya sini .

Jika Anda senang menggunakan fitur Hibernate Validator, Anda dapat menggunakan @ScriptAssert , yang disediakan sejak Validator-4.1.0.Final. Kecuali dari JavaDoc-nya:

Ekspresi skrip dapat ditulis dalam bahasa skrip atau ekspresi apa pun, di mana mesin yang kompatibel dengan JSR 223 ("Pembuatan Skrip untuk Platform JavaTM") dapat ditemukan di jalur kelas.

Contoh:

@ScriptAssert(lang = "javascript", script = "_this.value1 != null || _this != value2)")
public class MyBean {
  private String value1;
  private String value2;
}
Kuat
sumber
Ya, dan Java 6 menyertakan Rhino (mesin JavaScript) sehingga Anda dapat menggunakan JavaScript sebagai bahasa ekspresi tanpa menambahkan ketergantungan tambahan.
3
Berikut adalah contoh cara membuat validasi seperti itu dengan Hibernate Validator 5.1.1. Final
Ivan Hristov