0

一旦我们使用EclipseLink对我们的 JPA 实体使用静态编织,我们就会在我们的应用程序中得到异常。

该应用程序是一个使用spring-data-jpaspring-data-rest-webmvc提供 CRUD 功能来更改实体的 Web 应用程序。

当实体类不通过编织处理时,它可以工作。但是一旦我们使用编织实体,我们就会得到:

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: 
 Conflicting getter definitions for property "rootParentDescriptor":   
  org.eclipse.persistence.descriptors.InheritancePolicy#isRootParentDescriptor(0 params) vs org.eclipse.persistence.descriptors.InheritancePolicy#getRootParentDescriptor(0 params) (through reference chain:    org.springframework.hateoas.Resources["content"]->java.util.UnmodifiableCollection[0]->org.eclipse.persistence.internal.descriptors.changetracking.AttributeChangeListener["descriptor"]->org.eclipse.persistence.descriptors.RelationalDescriptor["inheritancePolicy"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Conflicting getter definitions for property "rootParentDescriptor": org.eclipse.persistence.descriptors.InheritancePolicy#isRootParentDescriptor(0 params) vs org.eclipse.persistence.descriptors.InheritancePolicy#getRootParentDescriptor(0 params) (through reference chain: org.springframework.hateoas.Resources["content"]->java.util.UnmodifiableCollection[0]->org.eclipse.persistence.internal.descriptors.changetracking.AttributeChangeListener["descriptor"]->org.eclipse.persistence.descriptors.RelationalDescriptor["inheritancePolicy"])

..

Caused by: java.lang.IllegalArgumentException: Conflicting getter definitions for property "rootParentDescriptor": org.eclipse.persistence.descriptors.InheritancePolicy#isRootParentDescriptor(0 params) vs org.eclipse.persistence.descriptors.InheritancePolicy#getRootParentDescriptor(0 params)
at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder.getGetter(POJOPropertyBuilder.java:190) ~[jackson-databind-2.2.2.jar:2.2.2]

当 JSON 响应应该被编组包含一个实体时,就会发生这种情况。实体很简单,只有 2 个属性,没有关联等。

以下是使用的版本:

<dependency>
 <groupId>org.springframework.data</groupId>
 <artifactId>spring-data-rest-webmvc</artifactId>
 <version>1.1.0.M1</version>
</dependency>

<dependency>
 <groupId>org.eclipse.persistence</groupId>
 <artifactId>org.eclipse.persistence.jpa</artifactId>
 <version>2.5.0</version>
</dependency>

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.3.2.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.2.2</version>
</dependency>

是否有支持此组合的配置或描述此类问题的已知问题?

4

2 回答 2

1

经过一些研究,我找到了一种解决方法,但也许有人知道更好的解决方案。原因是 spring-data-commons 中的 JpaMetamodelMappingContext 类在实体类中找到了由编织过程添加的变量。它将它们作为属性收集在其元模型 (JpaPersistentEntity) 中,尽管这些字段仅对 EclipseLink 的内部行为感兴趣。

我用自己的子类替换了 JpaMetamodelMappingContext,这样就不会收集这些字段(就像它们是 @Transient 一样)。这是通过 HACK 完成的。

在我们的 spring 配置中,我们向 jpa:repositories 添加了一个工厂类

<jpa:repositories base-package="de.viaboxx.vplan.database.dao"
                  entity-manager-factory-ref="entityManagerFactory"
                  factory-class="de.viaboxx.springframework.data.jpa.EclipseJpaRepositoryFactoryBean"/>

新类 EclipseJpaRepositoryFactoryBean 引入了 JpaMetamodelMappingContext 的新实现。这是源代码:(从 JpaRepositoryFactoryBean 复制的类,在 setEntityManager() 中进行了更改)

public class EclipseJpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends
    TransactionalRepositoryFactoryBeanSupport<T, S, ID> {

private EntityManager entityManager;

/**
 * The {@link EntityManager} to be used.
 *
 * @param entityManager the entityManager to set
 */
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
    this.entityManager = entityManager;
    setMappingContext(
            new EclipseJpaMetamodelMappingContext(entityManager.getMetamodel())); // <<-- this is the only change
}

/*
 * (non-Javadoc)
 *
 * @see org.springframework.data.repository.support.
 * TransactionalRepositoryFactoryBeanSupport#doCreateRepositoryFactory()
 */
@Override
protected RepositoryFactorySupport doCreateRepositoryFactory() {
    return createRepositoryFactory(entityManager);
}

/**
 * Returns a {@link RepositoryFactorySupport}.
 *
 * @param entityManager
 * @return
 */
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
    return new JpaRepositoryFactory(entityManager);
}

/*
 * (non-Javadoc)
 *
 * @see
 * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
 */
@Override
public void afterPropertiesSet() {

    Assert.notNull(entityManager, "EntityManager must not be null!");
    super.afterPropertiesSet();
}
}

过滤内部属性的 Hack 包含在类 EclipseJpaMetamodelMappingContext 中(包必须是 'org.springframework.data.jpa.mapping' 才能访问类 JpaPersistentEntityImpl!)

public class EclipseJpaMetamodelMappingContext extends JpaMetamodelMappingContext {
/**
 * Creates a new JPA {@link javax.persistence.metamodel.Metamodel} based {@link org.springframework.data.mapping.context.MappingContext}.
 *
 * @param model must not be {@literal null}.
 */
public EclipseJpaMetamodelMappingContext(Metamodel model) {
    super(model);
}

@Override
protected JpaPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
                                                         JpaPersistentEntityImpl<?> owner,
                                                         SimpleTypeHolder simpleTypeHolder) {
    // HACK: ignore fields added by eclipselink weaving
    // because they cause problems during JSON marshaling with spring-data-rest 1.1.0.M1
    if (field.getType().getName().startsWith("org.eclipse.") ||
            field.getType().equals(PropertyChangeListener.class)) {
        return new TransientPropertyToBeIgnored(field, descriptor, owner, simpleTypeHolder);
    } else {
        return super.createPersistentProperty(field, descriptor, owner, simpleTypeHolder);    // call super!
    }
}

static class TransientPropertyToBeIgnored extends AnnotationBasedPersistentProperty<JpaPersistentProperty>
        implements JpaPersistentProperty {

    @Override
    public boolean isTransient() {
        return true;  // this causes the property to be ignored!
    }

    public TransientPropertyToBeIgnored(Field field, PropertyDescriptor propertyDescriptor,
                                        PersistentEntity owner,
                                        SimpleTypeHolder simpleTypeHolder) {
        super(field, propertyDescriptor, owner, simpleTypeHolder);
    }

    @Override
    protected Association createAssociation() {
        return new Association<JpaPersistentProperty>(this, null);
    }
}
}

这对我们有用,无论是编织的还是非编织的实体。

使用Hibernate而不是 EclipseLink时不会出现该问题,但使用 Hibernate 需要进行jackson-hibernate-module和其他配置更改。

于 2013-07-24T14:40:29.367 回答
1

我现在通过将 JPA注释属性视为瞬态来解决此问题。@Transient因此,只要您的持久性提供程序正确使用那些您应该没问题。如果没有,我们将不得不针对它提交罚单。

该修复将在 Spring Data JPA 的 1.3.5 和 1.4 RC1 中提供。

于 2013-07-25T19:44:40.403 回答