当前位置: 首页 > 知识库问答 >
问题:

如何让Eclipselink在映射超类中激发JSR303约束?

宰父存
2023-03-14

在persist()操作期间,EclipsLink似乎不检测或激发基类中的JSR303注释约束,基类是实体的映射超类。

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>     

其他一些相关参数

  • 使用jpa 2.1--特别是eclipslink 2.6.2和2.6.3
  • 我是集成测试-所以java se(和spock)
  • JDK 1.8.77
  • 我的类路径中确实有hibernate验证器(org.hibernate:hibernate-validator:5.2.4.final)
  • 如果我编写一个测试fixture并直接使用validitor.validate()(没有jpa或persist),那么hibernate验证器将按预期工作。
  • 我不使用JPA注释,只使用ORM xml来声明实体映射。
  • 我确实使用JSR303注释来标记带有约束的attrs和props。
  • persistence.xml被标记为验证“auto”,并且已经尝试了许多不同的属性,如javax.persistence.validation.group.pre-persisted和标记接口的FQDN。

共有1个答案

屠和洽
2023-03-14

据我所见,eclipse Link2.6.x到2.6.4在维护触发JSR303 bean验证的契约方面似乎有一个巨大的bug。目前,Eclipselink2.6.4仅在子实体被右向外标记为约束时才触发这些验证。

我有在JEE6库版本(例如eclipselink 2.4.x)下完美工作的集成测试。

当我将库升级到JEE7 verions时,在ecliselink的特殊情况下,这意味着版本:2.6.1到2.6.4,它们都表现出相同的bug。

这足以调用验证器(例如,hibernate验证器)。

在本例中,我的the测试仍然失败,因为现在我得到了两个@NotNull约束验证,而不是一个。

在下面的代码片段中,我将说明Eclipselink2.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

eclipselink中的bug似乎是当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());
 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;
                    }
                }
            }
        }
Field f : ReflectionUtils.getDeclaredFields(clazz)

只返回当前类的字段,而不返回父类。

与此同时,我强迫自己做的是,在BeanValidationHelper中设置一个变通方法,以便将坏掉的算法用于检测需要验证的类:

 @Transient
    @NotNull
    private final char waitForEclipseLinkToFixTheVersion264 = 'a';

通过上述操作,您的代码可以用一个块清晰地标记出来,将来可以删除该块。因为场是瞬态的...嘿,它不会改变你的分贝。

这方面的解决办法,我还不确定。我将研究如何在PreInserPhase期间注册一个事件listner,它的作用与BeanValidationListner相同。或者,我将在本地修补BeanValidationListner.class以订阅PreINsert事件。

我不喜欢修改其他人维护的库代码,所以我将首先使用我们自己的eventListner作为这个bug的临时工作人员。

添加允许验证这两个bug的存储库。https://github.com/99sono/eclipselink_2_6_4_jsr_303bug

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 {

亲切的问候。

 类似资料:
  • 问题内容: 我们有一个有一些特殊要求的项目,其中之一是从Oracle 10g数据库的XMLType数据库列中获取数据。 我们已经找到了使用JDBC的简单解决方案,但是由于所有数据访问都是通过JPA(使用的实现是EclipseLink)来完成,因此它会使应用程序有些混乱。 我们已经进行了一些研究,找到了一些解决方案,例如使用Converters和其他辅助类型,但是实现似乎有些复杂。 因此,我的问题是

  • 假设中间CNN层的输入大小为512×512×128,在卷积层中,我们在步长2处应用48个7×7滤波器,没有填充。我想知道结果激活图的大小是多少?

  • 关于MapStruct的问题。我有这样的情况,我从基实体扩展类,但不知道如何映射它。这是我的箱子。 BaseEntity: 基础: 没有显示错误,在映射器实现(生成的代码)中没有该ID的映射:

  • 主要内容:1. 一对一,2. 一对多,3. 多对一,4. 多对多映射约束 映射约束是一种数据约束,表示通过关系集可以与另一个实体相关的实体的数量。 它在描述涉及两个以上实体集的关系集时最有用。 对于实体集A和B上的二元关系集R,有四种可能的映射基数。这些如下: 一对一(1:1) 一对多(1:M) 多对一(M:1) 多对多(M:M) 1. 一对一 在一对一映射中,E1中的实体与E2中的至多一个实体相关联,并且E2中的实体与E1中的至多一个实体相关联。 2. 一对

  • <![CDATA[插入MEMBER_SERVICE_RECORD(MEMBER_ID,SERVICE_DATE,service_milees,SERVICE_DESC,MODIFIED_DATE)值(?,?,?,?,?)]]>

  • 问题内容: 通用实体,超类: pojo: 我尝试使用那些注释:@ AttributeOverride,@ Id等,但是它不起作用。你能帮助我吗?我想覆盖属性“ id”以通过pojo / table指定另一个列名和一个序列。最好的方法是什么? 问题答案: 试试这个,代替