Jpa 如何让Eclipselink在映射的超类中激发JSR303约束?
EclipsLink在persist()操作期间似乎不会在作为实体的映射超类的基类中检测或激发JSR303注释约束 例如:Jpa 如何让Eclipselink在映射的超类中激发JSR303约束?,jpa,eclipselink,bean-validation,Jpa,Eclipselink,Bean Validation,EclipsLink在persist()操作期间似乎不会在作为实体的映射超类的基类中检测或激发JSR303注释约束 例如: public Base { @NotNull private Integer id; private String recordName; //other stuff (getters etc) } 然后 public class MyObject extends Base { //stuff... } 然后:
public Base
{
@NotNull
private Integer id;
private String recordName;
//other stuff (getters etc)
}
然后
public class MyObject
extends Base
{
//stuff...
}
然后:
<mapped-superclass class="Base">
<attributes>
<basic name="recordName">
<column name = "NAME" />
</basic>
</attributes>
</mapped-superclass>
)更多细节 在我看来,eclipse link 2.6.X到2.6.4在维护触发JSR 303 bean验证的契约方面似乎有一个巨大的缺陷。
现在,eclipselink 2.6.4只在您的子实体被直接标记为约束时触发这些验证
我有在JEE6库版本(例如eclipselink 2.4.x)下完美工作的集成测试
当我将库升级到JEE 7版本时,在ecliselink的特殊情况下,这意味着版本:2.6.1到2.6.4,它们都表现出相同的错误
到目前为止,我所分析的中断单元测试正在验证必须触发ConstraintViolationException(如NOTNULL)
因此,如果您使用一个扩展了抽象实体B的实体A,而抽象实体B是一个@MappedSuperClass。
如果在抽象实体B上发现@NotNull或任何其他此类约束,您将遇到问题。。。
在这种情况下,事情不会进展顺利
eclipselink不会触发任何约束冲突。
相反,如果您在测试中发出commit()或flush(),则DB会阻止您。
Eclipse链接将在db异常时回滚
但是,只要转到实体A并向其中注入一个虚拟字段:
@NotNull
私有字符串虚拟
这足以调用验证程序(例如hibernate验证程序)
在这种情况下,我的测试仍然失败,因为现在我得到两个@NotNull约束验证,而不是一个
在下面的代码片段中,我演示了eclipselink 2.6.1上的堆栈跟踪的相关chucnk
Caused by: javax.validation.ConstraintViolationException:
Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'prePersist'.
Please refer to embedded ConstraintViolations for details.
at org.eclipse.persistence.internal.jpa.metadata.listeners.BeanValidationListener.validateOnCallbackEvent(BeanValidationListener.java:108)
at org.eclipse.persistence.internal.jpa.metadata.listeners.BeanValidationListener.prePersist(BeanValidationListener.java:77)
at org.eclipse.persistence.descriptors.DescriptorEventManager.notifyListener(DescriptorEventManager.java:748)
at org.eclipse.persistence.descriptors.DescriptorEventManager.notifyEJB30Listeners(DescriptorEventManager.java:691)
at org.eclipse.persistence.descriptors.DescriptorEventManager.executeEvent(DescriptorEventManager.java:229)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.registerNewObjectClone(UnitOfWorkImpl.java:4314)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.registerNotRegisteredNewObjectForPersist(UnitOfWorkImpl.java:4291)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.registerNotRegisteredNewObjectForPersist(RepeatableWriteUnitOfWork.java:521)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.registerNewObjectForPersist(UnitOfWorkImpl.java:4233)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.persist(EntityManagerImpl.java:507)
at TEST_THAT_IS_PROBLEMATIC
... 25 more
在上面的堆栈跟踪中,您让单元测试在实体A上执行em.persist(),而本例中的实体A具有dummy@NotNull字段。因此将调用验证
eclipselink中的错误似乎是当BeanValidationListener询问BeanValidationHelper类是否受约束时:
Eclipselink的代码如下:
private void validateOnCallbackEvent(DescriptorEvent event, String callbackEventName, Class[] validationGroup) {
Object source = event.getSource();
boolean noOptimization = "true".equalsIgnoreCase((String) event.getSession().getProperty(PersistenceUnitProperties.BEAN_VALIDATION_NO_OPTIMISATION));
boolean shouldValidate = noOptimization || beanValidationHelper.isConstrained(source.getClass());
if (shouldValidate) {
Set<ConstraintViolation<Object>> constraintViolations = getValidator(event).validate(source, validationGroup);
if (constraintViolations.size() > 0) {
// There were errors while call to validate above.
// Throw a ConstrainViolationException as required by the spec.
// The transaction would be rolled back automatically
// TODO need to I18N this.
throw new ConstraintViolationException(
"Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'" +
callbackEventName + "'. Please refer to embedded ConstraintViolations for details.",
(Set<ConstraintViolation<?>>) (Object) constraintViolations); /* Do not remove the explicit
cast. This issue is related to capture#a not being instance of capture#b. */
}
}
}
private Boolean detectConstraints(Class<?> clazz) {
for (Field f : ReflectionUtils.getDeclaredFields(clazz)) {
for (Annotation a : f.getDeclaredAnnotations()) {
final Class<? extends Annotation> type = a.annotationType();
if (KNOWN_CONSTRAINTS.contains(type.getName())){
return true;
}
// Check for custom annotations on the field (+ check inheritance on class annotations).
// Custom bean validation annotation is defined by having @Constraint annotation on its class.
for (Annotation typesClassAnnotation : type.getAnnotations()) {
final Class<? extends Annotation> classAnnotationType = typesClassAnnotation.annotationType();
if (Constraint.class == classAnnotationType) {
KNOWN_CONSTRAINTS.add(type.getName());
return true;
}
}
}
}
返回false,这是完全错误的
最后,如果检查BeanValidationHelper的实现,代码的初始部分如下所示:
private void validateOnCallbackEvent(DescriptorEvent event, String callbackEventName, Class[] validationGroup) {
Object source = event.getSource();
boolean noOptimization = "true".equalsIgnoreCase((String) event.getSession().getProperty(PersistenceUnitProperties.BEAN_VALIDATION_NO_OPTIMISATION));
boolean shouldValidate = noOptimization || beanValidationHelper.isConstrained(source.getClass());
if (shouldValidate) {
Set<ConstraintViolation<Object>> constraintViolations = getValidator(event).validate(source, validationGroup);
if (constraintViolations.size() > 0) {
// There were errors while call to validate above.
// Throw a ConstrainViolationException as required by the spec.
// The transaction would be rolled back automatically
// TODO need to I18N this.
throw new ConstraintViolationException(
"Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'" +
callbackEventName + "'. Please refer to embedded ConstraintViolations for details.",
(Set<ConstraintViolation<?>>) (Object) constraintViolations); /* Do not remove the explicit
cast. This issue is related to capture#a not being instance of capture#b. */
}
}
}
private Boolean detectConstraints(Class<?> clazz) {
for (Field f : ReflectionUtils.getDeclaredFields(clazz)) {
for (Annotation a : f.getDeclaredAnnotations()) {
final Class<? extends Annotation> type = a.annotationType();
if (KNOWN_CONSTRAINTS.contains(type.getName())){
return true;
}
// Check for custom annotations on the field (+ check inheritance on class annotations).
// Custom bean validation annotation is defined by having @Constraint annotation on its class.
for (Annotation typesClassAnnotation : type.getAnnotations()) {
final Class<? extends Annotation> classAnnotationType = typesClassAnnotation.annotationType();
if (Constraint.class == classAnnotationType) {
KNOWN_CONSTRAINTS.add(type.getName());
return true;
}
}
}
}
仅返回当前类的字段,而不返回父类的字段
同时,我强迫自己做的是,在BeanValidationHelper中设置此解决方案,以冻结已损坏的算法,从而将类检测为需要验证的类:
@Transient
@NotNull
private final char waitForEclipseLinkToFixTheVersion264 = 'a';
通过如上所述的操作,您的代码已经清楚地标记了一个块,您可以在将来删除该块。
由于磁场是瞬态的。。。嘿,它不会改变你的数据库
同时请注意,eclipselink论坛现在有更多的信息。
这个bug比不正确跟踪BeanValidationListner.class中何时需要“beanValidation”更深入。
这个bug有第二个深度。
随eclipse链接提供的BeanValidationListner.class也未注册以下内容的任何实现:
PreWriteEvent和用于描述符ReventManager.PreInsertEvent
因此,当“DeferredCachedDetectionPolocy.class”为calculateInChanges()时,如果实体A具有JSR字段,但仍然没有获得JSR 303验证。
这很可能发生在您身上,因为您的enitya是:
T0:持久化并通过ok验证
T1:在同一事务中修改peristed实体,并且当calculateChanges调用事件Litenter时。
BeanValidationListner.class不关心预插入事件。
它只是假设验证是由prePersist完成的,并且根本不调用验证
这方面的工作,我还不确定。
我将研究如何在PreInserPase期间注册事件列表器,这与BeanValidationListner的功能相同。
或者,我将在本地修补BeanValidationListner.class以订阅PreINsert事件
我已经修改了其他人维护的库的代码,所以我将首先使用我们自己的eventListner作为这个bug的临时解决方案
添加允许验证两个bug的存储库。
对于bug 2,在EclipseLink2.6.4修复其bean验证编排逻辑之前,以下EventListner服务器可以临时解决
package jpa.eclipselink.test.bug2workaround;
import java.util.Map;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.descriptors.changetracking.DeferredChangeDetectionPolicy;
import org.eclipse.persistence.internal.jpa.deployment.BeanValidationInitializationHelper;
import org.eclipse.persistence.internal.jpa.metadata.listeners.BeanValidationListener;
/**
* Temporary work-around for JSR 303 bean validation flow in eclipselink.
*
* <P>
* Problem: <br>
* The
* {@link DeferredChangeDetectionPolicy#calculateChanges(Object, Object, boolean, org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet, org.eclipse.persistence.internal.sessions.UnitOfWorkImpl, org.eclipse.persistence.descriptors.ClassDescriptor, boolean)}
* during a flush will do one of the following: <br>
* {@code descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreInsertEvent, writeQuery)); }
* or <br>
*
* {@code descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreUpdateEvent, writeQuery)); }
*
* <P>
* WHe it does
* {@code descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreInsertEvent, writeQuery)); }
* the {@link BeanValidationListener} will not do anything. We want it to do bean validation.
*/
public class ForceBeanManagerValidationOnPreInsert extends DescriptorEventAdapter {
private static final Class[] DUMMY_GROUP_PARAMETER = null;
/**
* This is is the EJB validator that eclipselink uses to do JSR 303 validations during pre-update, pre-delete,
* pre-persist, but not pre-insert.
*
* Do not access this field directly. Use the {@link #getBeanValidationListener(DescriptorEvent)} api to get it, as
* this api will initialize the tool if necessary.
*/
BeanValidationListener beanValidationListener = null;
final Object beanValidationListenerLock = new Object();
/**
*
*/
public ForceBeanManagerValidationOnPreInsert() {
super();
}
/**
* As a work-around we want to do bean validation that the container is currently not doing.
*/
@Override
public void preInsert(DescriptorEvent event) {
// (a) get for ourselves an instances of the eclipse link " Step 4 - Notify internal listeners."
// that knows how to run JSR 303 validations on beans associated to descriptor events
BeanValidationListener eclipseLinkBeanValidationListenerTool = getBeanValidationListener(event);
// (b) let the validation listener run its pre-update logic on a preInsert it serves our purpose
eclipseLinkBeanValidationListenerTool.preUpdate(event);
}
/**
* Returns the BeanValidationListener that knows how to do JSR 303 validation. Creates a new instance if needed,
* otherwise return the already created listener.
*
* <P>
* We can only initialize our {@link BeanValidationListener} during runtime, to get access to the JPA persistence
* unit properties. (e.g. to the validation factory).
*
* @param event
* This event describes an ongoing insert, updetae, delete event on an entity and for which we may want
* to force eclipselink to kill the transaction if a JSR bean validation fails.
* @return the BeanValidationListener that knows how to do JSR 303 validation.
*/
protected BeanValidationListener getBeanValidationListener(DescriptorEvent event) {
synchronized (beanValidationListenerLock) {
// (a) initializae our BeanValidationListener if needed
boolean initializationNeeded = beanValidationListener == null;
if (initializationNeeded) {
beanValidationListener = createBeanValidationListener(event);
}
// (b) return the validation listerner that is normally used by eclipse link
// for pre-persist, pre-update and pre-delete so that we can force it run on pre-insert
return beanValidationListener;
}
}
/**
* Creates a new instance of the {@link BeanValidationListener} that comes with eclipse link.
*
* @param event
* the ongoing db event (e.g. pre-insert) where we want to trigger JSR 303 bean validation.
*
* @return A new a new instance of the {@link BeanValidationListener} .
*/
protected BeanValidationListener createBeanValidationListener(DescriptorEvent event) {
Map peristenceUnitProperties = event.getSession().getProperties();
ValidatorFactory validatorFactory = getValidatorFactory(peristenceUnitProperties);
return new BeanValidationListener(validatorFactory, DUMMY_GROUP_PARAMETER, DUMMY_GROUP_PARAMETER,
DUMMY_GROUP_PARAMETER);
}
/**
* Snippet of code taken out of {@link BeanValidationInitializationHelper}
*
* @param puProperties
* the persistence unit properties that may be specifying the JSR 303 validation factory.
* @return the validation factory that can check if a bean is violating business rules. Almost everyone uses
* hirbernate JSR 303 validation.
*/
protected ValidatorFactory getValidatorFactory(Map puProperties) {
ValidatorFactory validatorFactory = (ValidatorFactory) puProperties
.get(PersistenceUnitProperties.VALIDATOR_FACTORY);
if (validatorFactory == null) {
validatorFactory = Validation.buildDefaultValidatorFactory();
}
return validatorFactory;
}
}
亲切问候。我觉得org.eclipse.persistence.internal.jpa.metadata.beanvalidation.BeanValidationHelper中有一个bug。您是否检查了他们的bug追踪器或报告了一个bug。Bean验证和jpa是相互依赖的,所以可以使用jpa注释,但只使用Bean验证注释。您的示例在imo中应该有效。看起来像EclipseLink 2中的问题。6.x.有关更多详细信息,请参见此处()。
package jpa.eclipselink.test.bug2workaround;
import java.util.Map;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.descriptors.changetracking.DeferredChangeDetectionPolicy;
import org.eclipse.persistence.internal.jpa.deployment.BeanValidationInitializationHelper;
import org.eclipse.persistence.internal.jpa.metadata.listeners.BeanValidationListener;
/**
* Temporary work-around for JSR 303 bean validation flow in eclipselink.
*
* <P>
* Problem: <br>
* The
* {@link DeferredChangeDetectionPolicy#calculateChanges(Object, Object, boolean, org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet, org.eclipse.persistence.internal.sessions.UnitOfWorkImpl, org.eclipse.persistence.descriptors.ClassDescriptor, boolean)}
* during a flush will do one of the following: <br>
* {@code descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreInsertEvent, writeQuery)); }
* or <br>
*
* {@code descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreUpdateEvent, writeQuery)); }
*
* <P>
* WHe it does
* {@code descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreInsertEvent, writeQuery)); }
* the {@link BeanValidationListener} will not do anything. We want it to do bean validation.
*/
public class ForceBeanManagerValidationOnPreInsert extends DescriptorEventAdapter {
private static final Class[] DUMMY_GROUP_PARAMETER = null;
/**
* This is is the EJB validator that eclipselink uses to do JSR 303 validations during pre-update, pre-delete,
* pre-persist, but not pre-insert.
*
* Do not access this field directly. Use the {@link #getBeanValidationListener(DescriptorEvent)} api to get it, as
* this api will initialize the tool if necessary.
*/
BeanValidationListener beanValidationListener = null;
final Object beanValidationListenerLock = new Object();
/**
*
*/
public ForceBeanManagerValidationOnPreInsert() {
super();
}
/**
* As a work-around we want to do bean validation that the container is currently not doing.
*/
@Override
public void preInsert(DescriptorEvent event) {
// (a) get for ourselves an instances of the eclipse link " Step 4 - Notify internal listeners."
// that knows how to run JSR 303 validations on beans associated to descriptor events
BeanValidationListener eclipseLinkBeanValidationListenerTool = getBeanValidationListener(event);
// (b) let the validation listener run its pre-update logic on a preInsert it serves our purpose
eclipseLinkBeanValidationListenerTool.preUpdate(event);
}
/**
* Returns the BeanValidationListener that knows how to do JSR 303 validation. Creates a new instance if needed,
* otherwise return the already created listener.
*
* <P>
* We can only initialize our {@link BeanValidationListener} during runtime, to get access to the JPA persistence
* unit properties. (e.g. to the validation factory).
*
* @param event
* This event describes an ongoing insert, updetae, delete event on an entity and for which we may want
* to force eclipselink to kill the transaction if a JSR bean validation fails.
* @return the BeanValidationListener that knows how to do JSR 303 validation.
*/
protected BeanValidationListener getBeanValidationListener(DescriptorEvent event) {
synchronized (beanValidationListenerLock) {
// (a) initializae our BeanValidationListener if needed
boolean initializationNeeded = beanValidationListener == null;
if (initializationNeeded) {
beanValidationListener = createBeanValidationListener(event);
}
// (b) return the validation listerner that is normally used by eclipse link
// for pre-persist, pre-update and pre-delete so that we can force it run on pre-insert
return beanValidationListener;
}
}
/**
* Creates a new instance of the {@link BeanValidationListener} that comes with eclipse link.
*
* @param event
* the ongoing db event (e.g. pre-insert) where we want to trigger JSR 303 bean validation.
*
* @return A new a new instance of the {@link BeanValidationListener} .
*/
protected BeanValidationListener createBeanValidationListener(DescriptorEvent event) {
Map peristenceUnitProperties = event.getSession().getProperties();
ValidatorFactory validatorFactory = getValidatorFactory(peristenceUnitProperties);
return new BeanValidationListener(validatorFactory, DUMMY_GROUP_PARAMETER, DUMMY_GROUP_PARAMETER,
DUMMY_GROUP_PARAMETER);
}
/**
* Snippet of code taken out of {@link BeanValidationInitializationHelper}
*
* @param puProperties
* the persistence unit properties that may be specifying the JSR 303 validation factory.
* @return the validation factory that can check if a bean is violating business rules. Almost everyone uses
* hirbernate JSR 303 validation.
*/
protected ValidatorFactory getValidatorFactory(Map puProperties) {
ValidatorFactory validatorFactory = (ValidatorFactory) puProperties
.get(PersistenceUnitProperties.VALIDATOR_FACTORY);
if (validatorFactory == null) {
validatorFactory = Validation.buildDefaultValidatorFactory();
}
return validatorFactory;
}
}
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DESCRIMINATOR", length = 32)
@DiscriminatorValue("Bug2WorkAround")
@Entity
@EntityListeners({ ForceBeanManagerValidationOnPreInsert.class })
public class Bug2Entity2WithWorkAround extends GenericEntity {