8

我是一个 Java EE/EJB 菜鸟,但是从我收集的文档和其他帖子中,您无法在实体验证期间使用相同的 entitymanager/session 查询数据库。

通常,可移植应用程序的生命周期方法不应调用 EntityManager 或 Query 操作、访问其他实体实例或修改同一持久性上下文中的关系。 [43] 生命周期回调方法可以修改调用它的实体的非关系状态。

请问翻译?

这很抽象……可以用更具体的术语来解释吗?它导致的问题比它回答的要多。例如,如果我的实体有一个延迟加载的集合,我是否可以在验证期间访问它?该集合是“另一个实体”,需要一个似乎违反文档的数据库查询。

这种“生命周期要求”似乎很奇怪,因为某些验证确实需要查询数据库,这只是生活中的事实。

从其他帖子中,我还看到人们通过使用 entitymanagerfactory 创建新的实体管理器/会话来解决此查询问题。

这让我想到了两个关于使用 EntityManagers 和 Hibernate Validation 的问题:

  1. 是否有可能我有某种设计缺陷或滥用 Hibernate 验证,因为我需要在验证期间查询数据库?
  2. 鉴于我正在将 Java EE 与 JBoss 一起使用,我如何使用 EntityManagerFactory 注入我的验证器?

我试过这样的事情:

@Stateless
public class UserValidator implements ConstraintValidator<ValidUser, User> {
    @PersistenceUnit(unitName="blahblah")
    EntityManagerFactory emf;

    ...
}

但是 EMF 永远不会被注入。我猜@Stateless 标签变得无关紧要,因为我正在实现一个 ConstraintValidator 接口,这是 Hibernate Validator 工作所必需的。

那么从 Validator 获取 EntityManagerFactory 的一般模式是什么?

谢谢!

4

3 回答 3

6

通过一些评论和足够的搜索,我终于找到了一种有点“规范”的方式来回答我的问题。

但为了澄清问题,我的问题实际上是问两件事,它们有两个不同的答案:

  1. 你如何将东西注入到 Hibernate Validation 框架中使用的 Validator 中?
  2. 假设我们可以注入东西,注入 EntityManagerFactory 或 EntityManager 并在 JPA 生命周期事件期间使用它们进行查询是否安全?

首先回答第二个问题我会简单地说,强烈建议在验证期间使用第二个 EntityManager 进行查询。这意味着您应该注入一个 EntityManagerFactory 并为查询创建一个新的 EntityManager(而不是注入一个与创建生命周期事件相同的 EntityManager)。

一般来说,出于验证目的,您无论如何都只会查询数据库而不是插入/更新,所以这应该是相当安全的。

我在这里问了一个非常相关的 SO 问题。

现在回答问题1。

是的,完全可以将东西注入 Hibernate Validation 框架中使用的 Validators 中。要做到这一点,你需要做 3 件事:

  1. 创建一个自定义的 ConstraintValidatorFactory,它将创建框架中使用的验证器(覆盖 Hibernate 的默认工厂)。(我的示例使用 Java EE,而不是 Spring,所以我使用 BeanManager,但在 Spring 中您可能会为此使用 ApplicationContext)。
  2. 创建一个validation.xml 文件,它告诉Hibernate Validation 框架为ConstraintValidatorFactory 使用哪个类。确保此文件最终位于您的类路径中。
  3. 编写一个注入一些东西的验证器。

这是一个使用“托管”(可注入)验证器的自定义 ConstraintValidatorFactory 示例:

package com.myvalidator;

public class ConstraintInjectableValidatorFactory implements ConstraintValidatorFactory {

    private static BeanManager beanManager;

    @SuppressWarnings(value="unchecked")
    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> clazz) {
        // lazily initialize the beanManager
        if (beanManager == null) {
            try {
                beanManager = (BeanManager) InitialContext.doLookup("java:comp/BeanManager");
            } catch (NamingException e) {
                // TODO what's the best way to handle this?
                throw new RuntimeException(e);
            }
        }

        T result = null;

        Bean<T> bean = (Bean<T>) beanManager.resolve(beanManager.getBeans(clazz));
        // if the bean/validator specified by clazz is not null that means it has
        // injection points so get it from the beanManager and return it. The validator
        // that comes from the beanManager will already be injected.
        if (bean != null) {
            CreationalContext<T> context = beanManager.createCreationalContext(bean);
            if (context != null) {
                result = (T) beanManager.getReference(bean, clazz, context);
            }
        // the bean/validator was not in the beanManager meaning it has no injection
        // points so go ahead and just instantiate a new instance and return it
        } else {
            try {
                result = clazz.newInstance();
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
        }

        return result;
    }
}

这是一个示例validation.xml 文件,它告诉Hibernate Validator 使用哪个类作为ValidatorFactory:

<?xml version="1.0" encoding="UTF-8"?>
<validation-config
    xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.0.xsd">
    <constraint-validator-factory>
        com.myvalidator.ConstraintInjectableValidatorFactory
    </constraint-validator-factory>
</validation-config>

最后是一个带有注入点的验证器类:

public class UserValidator implements ConstraintValidator<ValidUser, User> {

    @PersistenceUnit(unitName="myvalidator")
    private EntityManagerFactory entityManagerFactory;

    private EntityManager entityManager;

    @Override
    public void initialize(ValidUser annotation) {
    }

    @Override
    public boolean isValid(User user, ConstraintValidatorContext context) {
        // validation takes place during the entityManager.persist() lifecycle, so
        // here we create a new entityManager separate from the original one that
        // invoked this validation
        entityManager = entityManagerFactory.createEntityManager();

        // use entityManager to query database for needed validation

        entityManager.close();
    }
}
于 2013-08-22T13:11:04.553 回答
0

我想我得到了您使用出色的 bean 验证 API 进行所有验证的愿望,但请记住,这不是必需的。

此外,请考虑以下两个要求:

  1. 密码不能为空。
  2. 用户不得使用与之前任何密码相同的密码。

第一个显然只取决于密码本身,我会将其归类为验证数据,因此这种验证属于数据层。

第二个取决于一条数据与许多其他实体或系统当前状态的关系。我会将其归类为属于业务层的东西。

也就是说,与其尝试将验证约束放在实体类上,不如将它们放在某个业务层类上(是的,如果你现在愿意的话,你甚至可以使用 bean 验证)。

例如,假设您有一个User具有当前密码字段的Passwords实体和一个您可以从中查询用户旧密码的实体。现在让您的用户数据访问对象:

@Stateful // yes stateful, need the same instance across method invocations
@ValidatePassword
public class UserDao {

    @PersistenceContext private EntityManager em;
    private String password;

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public boolean isValidPassword() {
        // use the em to find the old passwords
        // check that the submitted password is valid
    }

    public void savePassword() {
        // find the user
        // set the user's now valid password
    }
}

创建你的类级约束:

@Target( { TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = MyPasswordValidator.class)
public @interface ValidatePassword {

    String message() default "error message";

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

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

}

和验证器:

public class MyPasswordValidator implements ConstraintValidator<ValidatePassword, UserDao> {

    public void initialize(SelfValidating constraintAnnotation) {
        // get message, etc.
    }

    public boolean isValid(UserDao userDao, ConstraintValidatorContext constraintValidatorContext) {
        return userDao.isValidPassword();
    }
}

像这样的事情应该这样做。作为一个副作用,由于实际验证现在由 EJB 完成,如果您保留默认的跨国属性,验证逻辑本身将被处理。

于 2013-08-16T11:58:39.597 回答
0

请问翻译?

生命周期事件不应使用实体管理器,因为它可能导致回归。想象一下,在更新前事件期间,您修改了另一个实体。这应该在先前的更新前事件中生成另一个更新前事件。为避免此类问题,不鼓励使用实体管理器。

但是,如果您只想读取一些额外的数据,从概念上讲没有问题。Valation 隐式发生在更新前和插入前事件中。

如果您从不使用加载后事件,那么在生命周期事件中读取数据不应触发嵌套的生命周期事件。据我了解规范,查询实体不是严格禁止的,但强烈不鼓励。在这种情况下,它可能没问题。如果这行得通,你试过吗?

那么从 Validator 获取 EntityManagerFactory 的一般模式是什么?

注入仅适用于托管实体。当无法进行注入时,您应该能够进行良好的旧查找以获得实体管理器。但是,当使用第二个实体管理器时,可能会生成嵌套的生命周期事件。但是,如果您只做一些琐碎的事情,例如阅读旧密码列表,那应该没问题。

于 2013-08-21T06:44:58.407 回答