EclipsLink似乎并未在基类中检测或触发JSR303注释约束,该基类是persist()操作期间实体的映射超类。
例如:
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>
最后:
<entity class="MyObject">
<table name="TheTable"/>
<attributes>
<id name="id">
<column name="recordId" />
</id>
</attributes>
</entity>
其他一些相关参数:
如前所述,调用em.persist(myObjectInst)不会触发添加到类“Base”的任何303注释。
*是否有一些调整参数或开关我可以修补这将使这工作? *
注意:我对此进行了深入调试,可以看到org.eclipse.persistence.internal.jpa.metadata.beanvalidation.BeanValidationHelper.detectConstraints()不会查看JSR303注释的任何父类。它似乎只想查看特定的实体类。如果我将JSR303约束移动到具体(或实体类),我可能会猜测;它可能只是工作。但后来我会松开扩展和映射超类的东西。那有什么乐趣?
更新 看起来像EclipseLink 2.6.x中的问题。有关详细信息,请参见此处(https://www.eclipse.org/forums/index.php?t=msg&th=1077658&goto=1732842&#msg_1732842)。
答案 0 :(得分:2)
从我所看到的,eclipse链接2.6.X到2.6.4似乎在维护触发JSR 303 bean验证的合同方面存在Massive错误。 现在,eclipselink 2.6.4只会在您的子实体被标记为约束的右侧时触发这些验证。
我的集成测试在JEE 6库版本下完美运行(例如eclipselink 2.4.x)。
当我将库升级到JEE 7版本时,在ecliselink的特定情况下,这意味着版本:2.6.1到2.6.4,它们都表现出相同的错误。
到目前为止我分析过的单元测试已经过验证,必须触发ConstraintViolationExceptions,例如not null。
因此,如果您使用扩展抽象实体B的实体A.而抽象实体B是@MappedSuperClass。 如果在抽象实体B上找到@NotNull或任何其他此类约束,您将遇到问题... 在这种情况下,事情会进展顺利。
eclipselink不会触发约束违规。 相反,如果您在测试中发出commit()或flush(),则DB会阻止您。 Eclipse-link将在db异常上回滚。
然而,只要你去实体A然后你就把它抽成一个虚拟字段: @NotNull private String dummy;
这足以使Validator(例如hibernate验证器)被调用。
在这种情况下,我的测试仍然失败,因为现在我得到了两个@NotNull约束验证而不是一个。
在下面的代码片段中,我将介绍eclipselink 2.6.1上堆栈跟踪的相关信息。
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具有虚拟@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. */
}
}
}
问题在于查询:
beanValidationHelper.isConstrained(source.getClass());
返回false,这是完全错误的。
最后,如果检查BeanValidationHelper的实现,代码的初始部分如下所示:
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;
}
}
}
}
上面的实现显然是错误的,因为整个方法是非递归的,并且它从反射中使用的apis本身是非递归的。 他们只关注当前的实例。 如果您看到以下堆栈溢出线程:
What is the difference between getFields and getDeclaredFields in Java reflection
排名靠前的答案清楚地解释了:
Field f : ReflectionUtils.getDeclaredFields(clazz)
仅返回当前类的字段,但不返回父项。
我正在努力做的事情是同时实施这个解决方法,以便在BeanValidationHelper中破解算法,以检测需要验证的类:
@Transient
@NotNull
private final char waitForEclipseLinkToFixTheVersion264 = 'a';
通过执行上述操作,您可以清楚地将代码标记为可以在将来删除的块。 而且由于该领域是短暂的...嘿,它不会改变你的数据库。
请注意,eclipselink论坛现在还有其他信息。 这个错误比只是不正确地跟踪“beanValidation&#34;在BeanValidationListner.class中需要。 这个bug有第二个深度。 随eclipse-link提供的BeanValidationListner.class也没有为以下内容注册任何实现: PreWriteEvent和DescriptorEventManager.PreInsertEvent。
因此当&#34; DeferredCachedDetectionPolocy.class是calculatinChanges()时,如果你的实体A有JSR字段,它仍然没有得到JSR 303验证。 这很有可能发生在你身上,因为你的enitya是: T0:通过确定持久和有效 T1:您在同一事务中修改peristed实体,并且在calculateChanges调用事件litenters时。 BeanValidationListner.class不关心preInsertEvent。 它只是假设验证是在prePersist中完成的,并且根本不会调用验证。
解决这个问题,我还不确定。 我将研究如何在PreInserPhase期间注册事件列表器,它与BeanValidationListner相同。 或者,我将在本地修补BeanValidationListner.class以订阅PreINsert事件。
我讨厌修改其他人维护的图书馆代码,所以我会首先找到我们自己的eventListner的方法作为这个bug的临时工作。
添加允许验证这两个错误的存储库。 https://github.com/99sono/EclipseLink_2_6_4_JSR_303Bug
对于第2个错误,以下EventListner可以为临时解决方案提供服务,直到eclipse链接2.6.4修复了它们的bean验证编排逻辑。
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;
}
}
只需将此bean验证器添加到类中,最好是基本抽象类,以确保在插入前进行JSR 303验证。 这应该可以解决这个漏洞,它允许我们将违反业务规则的dirtyu实体提交给db。
以下是一个具有解决方法的实体示例。
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DESCRIMINATOR", length = 32)
@DiscriminatorValue("Bug2WorkAround")
@Entity
@EntityListeners({ ForceBeanManagerValidationOnPreInsert.class })
public class Bug2Entity2WithWorkAround extends GenericEntity {
亲切的问候。