在Hibernate验证期间执行EntityManager查询的正确方法

时间:2013-08-16 06:41:29

标签: hibernate java-ee entitymanager hibernate-validator

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

  

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

请翻译?

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

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

从其他帖子我也看到人们通过使用entitymanagerfactory创建一个新的entitymanager / session来解决这个查询问题。

这引出了两个关于使用EntityManagers和Hibernate验证的问题:

  1. 我是否有可能遇到某种设计缺陷或滥用Hibernate验证,因为我需要在验证期间查询数据库?
  2. 鉴于我在JBoss中使用Java EE,如何使用EntityManagerFactory注入验证器?
  3. 我尝试过这样的事情:

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

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

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

    谢谢!

3 个答案:

答案 0 :(得分:6)

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

但为了澄清问题,我的问题实际上是要求两个有两个不同答案的事情:

  1. 如何将内容注入Hibernate Validation框架中使用的Validators?
  2. 假设我们可以注入东西,注入EntityManagerFactory或EntityManager并在JPA生命周期事件中使用它们进行查询是否安全?
  3. 首先回答第二个问题我简单地说,强烈建议在验证期间使用第二个EntityManager进行查询。这意味着您应该注入一个EntityManagerFactory并为查询创建一个新的EntityManager(而不是注入一个EntityManager,它将与创建生命周期事件的EntityManager相同)。

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

    我问了一个非常相关的SO问题here

    现在回答问题1。

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

    1. 创建一个自定义ConstraintValidatorFactory,它将创建框架中使用的验证器(覆盖Hibernate的默认工厂)。 (我的例子使用的是Java EE,而不是Spring,所以我使用的是BeanManager,但在Spring中你可能会使用ApplicationContext。)
    2. 创建一个validation.xml文件,该文件告诉Hibernate Validation框架将哪个类用于ConstraintValidatorFactory。确保此文件最终在您的类路径上。
    3. 编写一个注入内容的验证器。
    4. 以下是使用“托管”(可注入)验证器的自定义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();
          }
      }
      

答案 1 :(得分:0)

我认为我希望使用awesome bean验证API进行所有验证,但请记住这不是必需的。

此外,请考虑这两个要求:

  1. 密码不能为空。
  2. 用户不得使用与以前任何密码相同的密码。
  3. 第一个显然只取决于密码本身,我会将其归类为验证数据,因此这种验证属于数据层。

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

    也就是说,不是试图将验证约束放在实体类上,而是将它们放在某个业务层类上(是的,如果您现在愿意,甚至可以使用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完成,因此如果保留默认的跨国属性,验证逻辑本身将被处理。

答案 2 :(得分:0)

  

请翻译?

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

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

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

  

那么获取EntityManagerFactory的一般模式是什么?   来自验证者?

注入仅适用于托管实体。如果无法注射,您应该可以做一个好的lookup to obtain an entity manager。但是,在使用第二个实体管理器时,可能会生成嵌套的生命周期事件。但是,如果你只做一些微不足道的事情,比如阅读旧密码列表,那应该没问题。