Different Hibernate validation annotations on same property
I am using two validation annotations on a property in the bean:
@NotEmpty(message = "{name.required}")
@Pattern(r开发者_如何学运维egex = "^([A-Za-z0-9]{2,}(\\-[a-zA-Z0-9])?)$", message = "{invalid.name}")
private String name;
If i left the name empty, I got the two errors but I want only the first error message (if the first condition occurs show its error message then skip the second condition).
if the first condition occurs show its error message then skip the second condition
This can be done by creating Composite Constraint and annotating it with @ReportAsSingleViolation meta constraint.
UserName.java
@ReportAsSingleViolation
@NotEmpty
@Pattern(regexp="^([A-Za-z0-9]{2,}(\\-[a-zA-Z0-9])?)$")
@Constraint(validatedBy = {})
public @interface UserName {
String message() default "invalid userName!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Reference 3.2. Constraint composition
The accepted answer doesn't work as you expect. Sure constraint composition is good only if you want to list ALL the errors in the ENTIRE composition chain. It doesn't work if you want to early exit from the first validation error.
The docs for @ReportAsSingleViolation
say The error reports of each individual composing constraint are ignored.
Using the accept example
@ReportAsSingleViolation
@NotEmpty
@Pattern(regexp="^([A-Za-z0-9]{2,}(\\-[a-zA-Z0-9])?)$")
@Constraint(validatedBy = {})
public @interface UserName {
String message() default "invalid userName!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
This means you will get the default message error of the UserName
annotation which is "invalid userName!" even if @NotEmpty fails first....
I must say I am quite shocked at how poor this design is by the java bean validation implementors. It makes absolutely no sense to have composed validations if you return a completely irrelevant message. It should fail first AND return the corresponding error for the validation that actually failed!. Anyway there is no way to do this without massive ugly hacks. Such a simple validation task turns into a nightmare. 0_o
My work around solution is don't compose validations, just create 1 validation and implement it all yourself. Its not DRY but its simple at least.
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
public @interface Password {
String message() default "{com.example.Password.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class PasswordValidator implements ConstraintValidator<Password, String> {
private Pattern twoDigitsPattern;
public void initialize(Password constraint) {
twoDigitsPattern = Pattern.compile("(.*[\\d]){2}");
}
public boolean isValid(String password, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
if (password == null) {
context.buildConstraintViolationWithTemplate("{javax.validation.constraints.NotNull.message}")
.addConstraintViolation();
return false;
}
if (password.length() < 5 || password.length() > 10) {
context.buildConstraintViolationWithTemplate("must be between 5 to 10 characters")
.addConstraintViolation();
return false;
}
if (!twoDigitsPattern.matcher(password).matches()) {
context.buildConstraintViolationWithTemplate("must contain 2 digits between [0-9]").addConstraintViolation();
return false;
}
return true;
}
}
精彩评论