ฉันจะตรวจสอบความถูกต้องสองฟิลด์ขึ้นไปร่วมกันได้อย่างไร


92

ฉันใช้การตรวจสอบความถูกต้อง JPA 2.0 / Hibernate เพื่อตรวจสอบโมเดลของฉัน ตอนนี้ฉันมีสถานการณ์ที่ต้องตรวจสอบการรวมกันของสองฟิลด์:

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

แบบจำลองไม่ถูกต้องหากทั้งสองอย่างgetValue1()และgetValue2()เป็นnullอย่างอื่นและถูกต้อง

ฉันจะทำการตรวจสอบความถูกต้องประเภทนี้ด้วย JPA 2.0 / Hibernate ได้อย่างไร? ด้วย@NotNullคำอธิบายประกอบแบบง่ายgetters ทั้งสองต้องไม่เป็นโมฆะเพื่อผ่านการตรวจสอบความถูกต้อง


คำตอบ:


102

สำหรับการตรวจสอบคุณสมบัติหลายรายการคุณควรใช้ข้อ จำกัด ระดับคลาส จาก Bean Validation Sneak Peek part II: custom constraints :

### ข้อ จำกัด ระดับชั้นเรียน

บางท่านได้แสดงความกังวลเกี่ยวกับความสามารถในการใช้ข้อ จำกัด ที่ครอบคลุมคุณสมบัติหลายอย่างหรือเพื่อแสดงข้อ จำกัด ซึ่งขึ้นอยู่กับคุณสมบัติหลายประการ ตัวอย่างคลาสสิกคือการตรวจสอบที่อยู่ ที่อยู่มีกฎที่ซับซ้อน:

  • ชื่อถนนค่อนข้างเป็นมาตรฐานและต้องมีความยาว จำกัด
  • โครงสร้างรหัสไปรษณีย์ขึ้นอยู่กับประเทศทั้งหมด
  • เมืองมักจะมีความสัมพันธ์กับรหัสไปรษณีย์และสามารถตรวจสอบข้อผิดพลาดบางอย่างได้ (หากสามารถเข้าถึงบริการตรวจสอบความถูกต้องได้)
  • เนื่องจากการพึ่งพาซึ่งกันและกันเหล่านี้ข้อ จำกัด ระดับคุณสมบัติที่เรียบง่ายจึงเหมาะสมกับการเรียกเก็บเงิน

โซลูชันที่นำเสนอโดยข้อกำหนดเกี่ยวกับการตรวจสอบความถูกต้องของถั่วคือสองเท่า:

  • มันมีความสามารถในการบังคับใช้ชุดของข้อ จำกัด ก่อนชุดข้อ จำกัด อื่น ๆ ผ่านการใช้กลุ่มและลำดับกลุ่ม หัวข้อนี้จะกล่าวถึงในรายการบล็อกถัดไป
  • อนุญาตให้กำหนดข้อ จำกัด ระดับชั้นเรียน

ข้อ จำกัด ระดับคลาสเป็นข้อ จำกัด ปกติ (คู่คำอธิบายประกอบ / การนำไปใช้งาน) ซึ่งใช้กับคลาสแทนที่จะเป็นคุณสมบัติ ข้อ จำกัด ระดับคลาสกล่าวแตกต่างกันได้รับอินสแตนซ์วัตถุ (แทนที่จะเป็นค่าคุณสมบัติ) ใน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 ถูกกลับด้านในตัวอย่าง และถูกต้อง () ใช้เวลา 2 พารามิเตอร์
Guillaume Husta

1
TYPEและRUNTIMEควรแทนที่ด้วยElementType.TYPEและRetentionPolicy.RUNTIMEตามลำดับ
mark.monteiro

2
@ mark.monteiro คุณสามารถใช้การนำเข้าแบบคงที่: import static java.lang.annotation.ElementType.*;และimport static java.lang.annotation.RetentionPolicy.*;
cassiomolin

2
ฉันได้เขียนตัวอย่างใหม่เพื่อทำงานกับ Bean Validation ได้ดูที่นี่
Cassiomolin

1
พารามิเตอร์ของคำอธิบายประกอบไม่อยู่ในข้อกำหนดที่ถูกต้องเนื่องจากจะต้องมีข้อความกลุ่มและน้ำหนักบรรทุกเหมือนที่ Cassio กล่าวถึงภายใต้คำตอบนี้
Peter S.

38

เพื่อให้ทำงานอย่างถูกต้องกับ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;
        }
    }
}

วิธีบูตสแตรปหรือเรียกใช้โปรแกรมตรวจสอบความถูกต้องแบบกำหนดเองในโปรเจ็กต์ WebSphere restful สำหรับ CDI bean ฉันเขียนทั้งหมดแล้ว แต่ข้อ จำกัด ที่กำหนดเองไม่ทำงานหรือถูกเรียก
BalaajiChander

ฉันติดอยู่กับการตรวจสอบความถูกต้องที่คล้ายกัน แต่ของฉันisoA2Codeถูกเก็บไว้ในCountryตารางDB เป็นความคิดที่ดีที่จะโทรหา DB จากที่นี่หรือไม่? นอกจากนี้ฉันต้องการเชื่อมโยงหลังจากการตรวจสอบความถูกต้องเนื่องจากAddress belongs_toa Countryและฉันต้องการให้addressรายการมีcountryคีย์ต่างประเทศของตาราง ฉันจะเชื่อมโยงประเทศกับที่อยู่ได้อย่างไร
krozaine

โปรดทราบว่าเมื่อคุณตั้งค่าหมายเหตุประกอบการตรวจสอบประเภทที่ออบเจ็กต์ที่ไม่ถูกต้องข้อยกเว้นจะถูกโยนทิ้งโดยกรอบงานการตรวจสอบความถูกต้องของถั่ว ตัวอย่างเช่นหากคุณจะตั้งค่า@ValidAddressคำอธิบายประกอบที่วัตถุประเทศคุณจะได้รับNo validator could be found for constraint 'com.example.validation.ValidAddress' validating type 'com.example.Country'ข้อยกเว้น
Jacob van Lingen

12

เครื่องมือตรวจสอบระดับชั้นเองเป็นวิธีที่จะไปเมื่อคุณต้องการที่จะอยู่กับถั่วการตรวจสอบสเปคตัวอย่างที่นี่

หากคุณพอใจที่จะใช้คุณสมบัติ Hibernate Validator คุณสามารถใช้@ScriptAssertซึ่งมีให้ตั้งแต่ Validator -4.1.0 Exceprt จาก JavaDoc:

นิพจน์สคริปต์สามารถเขียนด้วยภาษาสคริปต์หรือนิพจน์ใดก็ได้ซึ่งเอ็นจิ้นที่เข้ากันได้กับJSR 223 ("Scripting for the Java ™ Platform") สามารถพบได้บน classpath

ตัวอย่าง:

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

ใช่และ Java 6 มี Rhino (JavaScript engine) ดังนั้นคุณสามารถใช้ JavaScript เป็นภาษานิพจน์ได้โดยไม่ต้องเพิ่มการอ้างอิงเพิ่มเติม

3
นี่คือตัวอย่างวิธีสร้างการตรวจสอบความถูกต้องด้วย Hibernate Validator 5.1.1 รอบชิงชนะเลิศ
Ivan Hristov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.