Correct way to do an EntityManager query during Hibernate Validation
我有点Java EE / EJB,但是从我收集的文档和其他帖子中,您不能在实体验证期间使用相同的entitymanager / session查询数据库。
In general, the lifecycle method of a portable application should not invoke EntityManager
or Query operations, access other entity instances, or modify relationships within the
same persistence context.[43] A lifecycle callback method may modify the non-relationship
state of the entity on which it is invoked.
请翻译?
这是非常抽象的...可以用更具体的术语来解释吗?它导致的问题多于答案。例如,如果我的实体有一个延迟加载的集合,是否可以在验证期间访问它?该集合是"另一个实体",并且将需要进行数据库查询,这似乎违反了文档。
这个"生命周期要求"看起来很奇怪,因为某些验证确实确实需要查询数据库,这只是生活中的事实。
在其他帖子中,我还看到人们通过使用entitymanagerfactory创建一个新的entitymanager / session来解决此查询问题。
这使我想到两个有关使用EntityManagers和Hibernate Validation的问题:
我尝试过这样的事情:
1 2 3 4 5 6 7 | @Stateless public class UserValidator implements ConstraintValidator<ValidUser, User> { @PersistenceUnit(unitName="blahblah") EntityManagerFactory emf; ... } |
但是EMF永远不会被注入。我猜@Stateless标记变得无关紧要,因为我正在实现ConstraintValidator接口,这对于Hibernate Validator起作用是必需的。
那么从验证器获取EntityManagerFactory的一般模式是什么?
谢谢!
通过一些评论和足够的讨论,我终于找到了某种"规范"的方式来回答我的问题。
但是要弄清楚,我的问题确实是在问两件事,它们有两个不同的答案:
首先回答第二个问题,我简单地说,强烈建议在验证过程中使用第二个EntityManager进行查询。这意味着您应该注入一个EntityManagerFactory并为查询创建一个新的EntityManager(而不是注入一个EntityManager,后者将与创建生命周期事件的对象相同)。
通常来说,出于验证目的,您无论如何都只会查询数据库,而不会插入/更新,因此这样做相当安全。
我在这里问了一个非常相关的SO问题。
现在回答问题1。
是的,完全有可能将事物注入到Hibernate Validation框架中使用的Validator中。为此,您需要做3件事:
这是使用\\'managed \\'(可注入)验证器的示例自定义ConstraintValidatorFactory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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:
1 2 3 4 5 6 7 8 9 | <?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> |
最后是一个带有注入点的验证器类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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(); } } |
Translation please?
生命周期事件不应使用实体管理器,因为它可能导致回归。想象一下,在更新前事件中,您修改了另一个实体。这应该在先前的预更新事件内生成另一个预更新事件。为避免此类问题,不建议使用实体管理器。
但是,如果您只想读取一些其他数据,从概念上讲就没有问题。评估在更新前和插入前事件中隐式发生。
如果您从不使用加载后事件,则在生命周期事件中读取数据不应触发嵌套的生命周期事件。据我了解,规范并不是严格禁止查询实体,而是强烈建议不要这样做。在这种情况下,可能没问题。您是否尝试过这种方法?
So what's the general pattern for getting at an EntityManagerFactory
from a Validator?
注入仅在受管实体中有效。如果无法进行注入,则应该能够进行良好的旧查找以获取实体管理器。但是,使用第二个实体管理器时,可能会生成嵌套的生命周期事件。但是,如果您只做一些琐碎的事情,例如阅读旧密码列表,那应该没问题。
我想我希望您使用令人敬畏的bean验证API进行所有验证,但是请记住这不是必需的。
此外,请考虑以下两个要求:
第一个显然仅取决于密码本身,我将其归类为验证数据,因此这种验证属于数据层。
第二个参数取决于一条数据与许多其他实体或系统当前状态之间的关系。我将其归类为业务层中的某物。
这就是说,不要将验证约束放在实体类上,而是将它们放在某个业务层类上(是的,如果您现在愿意,甚至可以使用bean验证)。
例如,假设您有一个带有当前密码字段的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @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 } } |
创建您的类级别约束:
1 2 3 4 5 6 7 8 9 10 11 12 | @Target( { TYPE }) @Retention(RUNTIME) @Constraint(validatedBy = MyPasswordValidator.class) public @interface ValidatePassword { String message() default"error message"; Class< ? >[] groups() default {}; Class<? extends Payload>[] payload() default {}; } |
和验证者:
1 2 3 4 5 6 7 8 9 10 | 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完成的,因此,如果保留默认的跨国属性为
,则验证逻辑本身将被事务处理。