Spring Hibernate 验证 - 每个字段收到一条错误消息

Spring Hibernate validation - getting one error message per field

我正在努力解决每个字段只收到一条错误消息的问题。我对每个领域都有很多规则,我想一一验证它们。如果失败,验证将停止,并且 return 只有一条消息描述该字段的失败规则。

经过研究,我发现了类似 @ReportAsSingleViolation 注释的东西,它有点管用,但它有来自自定义约束的固定消息。所以这不是我想要的。

我读过有关@GroupSequence 的内容,但我无法像我描述的那样工作。

这是我的具有自定义约束规则的实体:

@Entity
@Table(name = "users", schema = "myschema")
public class User {
    private int id;

    @ValidLogin
    private String login;

    @ValidPassword
    private String password;

    @ValidEmail
    private String email;

    //getters & setters
}

并使用几个内置规则实现我的自定义约束:

@Constraint(validatedBy = UsernameValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@NotEmpty
@Pattern(regexp = "^[a-zA-Z0-9]*$")
@Length.List({
        @Length(min = 3 , message = "{Length.min.user.login}"),
        @Length(max = 30, message = "{Length.max.user.login}")
})
public @interface ValidLogin {

    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default { };
}

默认情况下,我会在 jsp 视图中收到有关失败规则的每条消息。所以,我又想让它像这样工作:检查规则@NotEmpty,如果失败,return 适当的消息,如果不验证下一条规则@Pattern 等等。

你能帮忙吗?非常感谢!

每个注释都有 groups 属性,可用于将支票分组:

public class MyForm {

    @NotEmpty(groups = LoginLevel1.class)
    @Pattern(regexp = "^[a-zA-Z0-9]*$", groups = LoginLevel2.class)
    @Length.List({
        @Length(min = 3 , message = "{Length.min.user.login}", groups = LoginLevel3.class),
        @Length(max = 30, message = "{Length.max.user.login}", groups = LoginLevel3.class)
    })
    private String login;
}

下一步是将这些组与 @GroupSequence 分组,以允许具有快速失败行为:

public class MyForm {
    ...
    @GroupSequence({
        LoginLevel1.class,
        LoginLevel2.class,
        LoginLevel3.class
    })
    public interface LoginChecks {}

    public interface LoginLevel1 {}
    public interface LoginLevel2 {}
    public interface LoginLevel3 {}
}

最后一步是指示 Spring 使用这些组序列进行验证:

@PostMapping("/form-handler")
public String processForm(@Validated({MyForm.LoginChecks.class, MyForm.PasswordChecks.class}) MyForm form, BindingResult result) {

    if (result.hasErrors()) {
        return null;
    }
    ...
}

我想知道 GroupSequence 是否因为自定义约束而无法工作,是的,这就是原因。

我在自定义约束中应用了内置规则,但它们的组不起作用。在自定义约束中,只有一组 DEFAULT 可见,因为:

Class<?>[] groups() default { };

当我将这些内置规则从自定义约束移动到字段(现在更难看,我想保持实体漂亮)时,这有效。

但我们又来了。现在它必须 "level" 到 "level" 意味着当一个字段为空,而其他字段不是时,空字段只有一条消息。其他人即使无效,也在等待下一个 "level" 序列。这又是 - 不是我想要的。

对于 spring / hibernate 来说,每个字段出现一个错误似乎太多了。

如果有人知道如何让它工作,请告诉我,我会试一试。

这就是我认为您正在寻找的东西:

@Test
public void test() {
    Validator v = Validation.byProvider( HibernateValidator.class )
            .configure()
            .buildValidatorFactory()
            .getValidator();

    // validations for each group - shows only corresponding violations even if other constraints
    // are violated as well
    assertThat( v.validate( new Bar( null, null ), First.class ) ).hasSize( 2 );
    assertThat( v.validate( new Bar( "", "" ), Second.class ) ).hasSize( 2 );
    assertThat( v.validate( new Bar( "a", "a" ), Third.class ) ).hasSize( 2 );

    // shows that validation will go group by group as defined in the sequence:
    //NotNull
    Set<ConstraintViolation<Bar>> violations = v.validate( new Bar( null, null ) );
    assertThat( violations ).hasSize( 2 );
    assertThat( violations ).extracting( "message" ).containsOnly( "must not be null" );

    //NotBlank
    violations = v.validate( new Bar( "", "" ) );
    assertThat( violations ).hasSize( 2 );
    assertThat( violations ).extracting( "message" ).containsOnly( "must not be blank" );

    //Size
    violations = v.validate( new Bar( "a", "a" ) );
    assertThat( violations ).hasSize( 2 );
    assertThat( violations ).extracting( "message" ).containsOnly( "size must be between 5 and 2147483647" );

}

@GroupSequence({ First.class, Second.class, Third.class, Bar.class })
private static class Bar {

    @NotNull(groups = First.class)
    @NotBlank(groups = Second.class)
    @Size(min = 5, groups = Third.class)
    private final String login;

    @NotNull(groups = First.class)
    @NotBlank(groups = Second.class)
    @Size(min = 5, groups = Third.class)
    private final String password;

    public Bar(String login, String password) {
        this.login = login;
        this.password = password;
    }
}

interface First {
}

interface Second {
}

interface Third {
}

我添加了一个测试,因此可以看到验证是如何逐组进行的。要有这样的行为,您需要为您的 bean 重新定义一个默认的组序列。为此,您需要在要验证的 bean 上放置 @GroupSequence 注释,然后列出您需要的所有组并且不要忘记添加 bean class 本身(就像在这个例子)。此外,所有这些信息都存在 here - 在文档中。


编辑

如果您可以不使用标准约束,那么您可以这样做:

    @Test
public void test2() throws Exception {
    Set<ConstraintViolation<Foo>> violations = validator.validate( new Foo( "", null ) );
    assertThat( violations ).hasSize( 2 );
    assertThat( violations ).extracting( "message" )
            .containsOnly( "value should be between 3 and 30 chars long", "Value cannot be null" );
}

private static class Foo {

    @ValidLogin
    private final String login;

    @ValidLogin
    private final String password;

    public Foo(String login, String password) {
        this.login = login;
        this.password = password;
    }
}

@Target({ FIELD })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { ValidLogin.ValidLoginValidator.class })
@interface ValidLogin {
    String message() default "message";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    class ValidLoginValidator implements ConstraintValidator<ValidLogin, String> {
        private static final Pattern PATTERN = Pattern.compile( "^[a-zA-Z0-9]*$" );

        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            String message = "";
            if ( value == null ) {
                message = "Value cannot be null";
            }
            else if ( !PATTERN.matcher( value ).matches() ) {
                message = "Value should match pattern ";
            }
            else if ( message.length() < 3 || message.length() > 30 ) {
                message = "value should be between 3 and 30 chars long";
            }
            if ( !message.isEmpty() ) {
                context.disableDefaultConstraintViolation();
                context.buildConstraintViolationWithTemplate( message ).addConstraintViolation();
            }
            return false;
        }
    }
}

在这种情况下,您只有自己的自定义约束和验证器。然后你逐个检查,然后根据第一次失败的检查构建违规。如果您对登录名和密码执行类似的检查但例如字符串长度的模式不同,您也可以将 patternminmax 之类的内容提取为约束的属性。 .