3

我的问题更多是关于性能而不是功能。我正在处理注册表单的电子邮件字段。User对于它的验证,我在实体中为电子邮件格式使用注释,并EmailExistValidator在数据库中检查此电子邮件是否已被使用

@Entity
@Table(name = "Users")
public class User {

    @Column(name = "email")
    @NotNull(message = "Email address required")
    @Pattern(regexp = "([^.@]+)(\\.[^.@]+)*@([^.@]+\\.)+([^.@]+)", message = "Invalid email address")
    private String email;

    // ... (other fields and getters/setters here)
}

验证者:

@FacesValidator(value = "emailExistValidator")
public class EmailExistValidator implements Validator {

    private static final String EMAIL_ALREADY_EXISTS = "This email address is already used.";

    @EJB
    private UserDao userDao;

    @Override
    public void validate(FacesContext context, UIComponent component, Object value)
            throws ValidatorException {
        String email = (String) value;
        try {
            if (email != null && userDao.findByEmail(email) != null) {
                throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
                        EMAIL_ALREADY_EXISTS, null));
            }
        } catch (DaoException e) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(),
                    null);
            FacesContext facesContext = FacesContext.getCurrentInstance();
            facesContext.addMessage(component.getClientId(facesContext), message);
        }
    }
}

根据我的测试,如果电子邮件未验证正则表达式模式,则仍会应用验证器。我不喜欢执行数据库查询以检查电子邮件是否存在的想法,而我已经知道这封电子邮件甚至无效。它确实会降低性能(在表单中的模糊事件上使用 ajax 时更是如此)。

我有一些猜测,但我找不到明确的答案,这就是我在这里问几个问题的原因:

  1. 将验证器与注释约束混合在一起是一件坏事吗?

  2. 在验证过程中,是否会检查每个约束,即使其中一个没有通过?(如果是,那么是所有消息(对于每个约束)都添加到FacesContext组件中,还是只是第一条消息?)

  3. 如果问题 2 为“是”,是否有任何方法可以在未验证一个约束时强制停止验证?

  4. 如果问题 2 为否或问题 3 为是,是否在某处指定了约束/验证器的应用顺序?这个订单可以定制吗?

如果这很重要,我将 PrimeFaces 用于我的 Facelets 组件。

我知道我可以将所有内容放入验证器并随时停止,但此验证器仅用于注册表单。当用于登录时,我只会检查实体的注释约束,而不是“已经存在”部分。此外,在我看来,注释和单约束验证器比多约束验证器的内容更具可读性。

4

1 回答 1

2

JSF 验证在 JSR303 bean 验证之前按设计运行。因此,当 bean 验证失败时,技术上没有办法跳过 JSF 验证。如果它们反过来运行,那么理论上您只需签UIInput#isValid()入 JSF 验证器即可:

UIInput input = (UIInput) component;

if (!input.isValid()) {
    return;
}

不幸的是,没有提供 API 的方法来控制 JSF-JSR303 验证顺序。您最好的选择是将 JSF 验证器转换为真正的 JSR303 bean 验证器,并将其分配给您声明在默认组之后运行的不同组。在 JSR303 中,约束是在每个组的基础上进行验证的。如果一个验证组失败,则不会执行任何后续验证组。

首先创建一个自定义 JSR303 bean 验证约束注解

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
@Documented
public @interface UniqueEmail {
    String message() default "{invalid.unique.email}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

然后创建一个自定义 JSR303 bean 验证约束验证器

public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {

    @Override
    public void initialize(UniqueEmail annotation) {
        // Grab EJB here via JNDI?
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return userDao.findByEmail(value) == null;
    }

}

不幸@EJB/@Inject的是,在ConstraintValidatorJava EE 6 / JSR303 Bean Validation 1.0 中不原生支持。它仅在 Java EE 7 / JSR303 Bean Validation 1.1 中受支持。然而,它们可以通过 JNDI 获得。

然后创建一个自定义验证组

public interface ExpensiveChecks {}

最后在您的实体上使用它,从而通过@GroupSequence注释声明组排序(默认组由当前类标识):

@Entity
@Table(name = "Users")
@GroupSequence({ User.class, ExpensiveChecks.class })
public class User {

    @Column(name = "email")
    @NotNull(message = "Email address required")
    @Pattern(regexp = "([^.@]+)(\\.[^.@]+)*@([^.@]+\\.)+([^.@]+)", message = "Invalid email address")
    @UniqueEmail(groups = ExpensiveChecks.class, message = "This email address is already used")
    private String email;

    // ...
}
于 2013-11-07T12:13:35.577 回答