Làm cách nào để xác thực hai hoặc nhiều trường kết hợp?


92

Tôi đang sử dụng xác thực JPA 2.0 / Hibernate để xác thực các mô hình của mình. Bây giờ tôi có một tình huống trong đó sự kết hợp của hai trường phải được xác thực:

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

Mô hình không hợp lệ nếu cả hai getValue1()getValue2()nullvà hợp lệ nếu không.

Làm cách nào để thực hiện loại xác thực này với JPA 2.0 / Hibernate? Với một @NotNullchú thích đơn giản, cả hai getters phải khác rỗng để vượt qua xác thực.


Câu trả lời:


102

Để xác thực nhiều thuộc tính, bạn nên sử dụng các ràng buộc cấp lớp. Từ Bean Validation Sneak Peek phần II: các ràng buộc tùy chỉnh :

### Ràng buộc cấp lớp

Một số bạn đã bày tỏ lo ngại về khả năng áp dụng một ràng buộc bao gồm nhiều thuộc tính hoặc để thể hiện ràng buộc phụ thuộc vào một số thuộc tính. Ví dụ cổ điển là xác thực địa chỉ. Địa chỉ có các quy tắc phức tạp:

  • tên đường có phần chuẩn và chắc chắn phải có giới hạn về độ dài
  • cấu trúc mã zip hoàn toàn phụ thuộc vào quốc gia
  • thành phố thường có thể được tương quan với một mã zip và một số kiểm tra lỗi có thể được thực hiện (miễn là có thể truy cập được dịch vụ xác thực)
  • bởi vì những phụ thuộc lẫn nhau này, một ràng buộc mức tài sản đơn giản không phù hợp với hóa đơn

Giải pháp được cung cấp bởi thông số kỹ thuật của Bean Validation là hai lần:

  • nó cung cấp khả năng buộc một tập hợp các ràng buộc được áp dụng trước một tập hợp các ràng buộc khác thông qua việc sử dụng các nhóm và trình tự nhóm. Chủ đề này sẽ được đề cập trong mục blog tiếp theo
  • nó cho phép xác định các ràng buộc cấp lớp

Các ràng buộc mức lớp là các ràng buộc thông thường (bộ đôi chú thích / triển khai) áp dụng trên một lớp chứ không phải một thuộc tính. Nói cách khác, các ràng buộc cấp lớp nhận thể hiện đối tượng (thay vì giá trị thuộc tính) trong 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;
        }
        // ...
    }
}

Các quy tắc xác thực địa chỉ nâng cao đã được bỏ qua đối tượng địa chỉ và được thực hiện bởi MultiCountryAddressValidator. Bằng cách truy cập cá thể đối tượng, các ràng buộc mức lớp có rất nhiều tính linh hoạt và có thể xác thực nhiều thuộc tính tương quan. Lưu ý rằng thứ tự nằm ngoài phương trình ở đây, chúng ta sẽ quay lại với nó trong bài viết tiếp theo.

Nhóm chuyên gia đã thảo luận về các cách tiếp cận hỗ trợ nhiều thuộc tính khác nhau: chúng tôi nghĩ rằng cách tiếp cận ràng buộc mức lớp cung cấp đủ sự đơn giản và linh hoạt so với các cách tiếp cận mức thuộc tính khác liên quan đến các phụ thuộc. Phản hồi của bạn được hoan nghênh.


17
Giao diện ConstraintValidator và chú thích @Constraint đã được đảo ngược trong ví dụ. Và là valid () nhận 2 tham số.
Guillaume Husta

1
TYPERUNTIMEnên được thay thế bằng ElementType.TYPERetentionPolicy.RUNTIME, tương ứng.
mark.monteiro

2
@ mark.monteiro Bạn có thể sử dụng nhập khẩu tĩnh: import static java.lang.annotation.ElementType.*;import static java.lang.annotation.RetentionPolicy.*;
cassiomolin

2
Tôi đã viết lại ví dụ để làm việc với Bean Validation. Hãy xem ở đây .
cassiomolin

1
Các thông số của chú thích không nằm trong đặc điểm kỹ thuật chính xác, vì phải có một thông báo, nhóm và tải trọng như đã được Cassio đề cập trong câu trả lời này.
Peter S.

38

Để hoạt động bình thường với Bean Validation , ví dụ được cung cấp trong câu trả lời của Pascal Thivent có thể được viết lại như sau:

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

Làm thế nào để khởi động hoặc gọi trình xác nhận tùy chỉnh trong một dự án còn lại của WebSphere cho một bean CDI? Tôi đã viết tất cả nhưng tùy chỉnh chế không hoạt động hoặc gọi
BalaajiChander

Tôi bị mắc kẹt với một xác nhận tương tự, nhưng của tôi isoA2Codeđược lưu trữ trong Countrybảng DB . Thực hiện một cuộc gọi DB từ đây có phải là một ý tưởng hay không? Ngoài ra, tôi muốn liên kết chúng sau khi xác thực vì Address belongs_toa Countryvà tôi muốn addressmục nhập có countrykhóa ngoại của bảng. Tôi sẽ liên kết quốc gia với địa chỉ như thế nào?
krozaine

Lưu ý rằng khi bạn đặt chú thích xác thực kiểu tại một đối tượng sai, một ngoại lệ sẽ được đưa ra bởi khung xác thực Bean. Ví dụ: nếu bạn đặt @ValidAddresschú thích tại đối tượng Quốc gia, bạn sẽ nhận được một No validator could be found for constraint 'com.example.validation.ValidAddress' validating type 'com.example.Country'ngoại lệ.
Jacob van Lingen

12

Trình xác thực cấp lớp tùy chỉnh là cách để thực hiện, khi bạn muốn ở lại với đặc tả Xác thực Bean, ví dụ ở đây .

Nếu bạn hài lòng khi sử dụng tính năng Hibernate Validator, bạn có thể sử dụng @ScriptAssert , được cung cấp từ Validator-4.1.0.Final. Exceprt khỏi JavaDoc của nó:

Các biểu thức tập lệnh có thể được viết bằng bất kỳ ngôn ngữ lập trình hoặc kịch bản nào mà công cụ tương thích JSR 223 ("Tập lệnh cho Nền tảng JavaTM") có thể được tìm thấy trên classpath.

Thí dụ:

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

Có, và Java 6 bao gồm Rhino (công cụ JavaScript) nên bạn có thể sử dụng JavaScript làm ngôn ngữ biểu thức mà không cần thêm các phụ thuộc bổ sung.

3
Dưới đây là một ví dụ về cách tạo một xác thực như vậy với Hibernate Validator 5.1.1. Cuối cùng
Ivan Hristov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.