简体   繁体   中英

Spring-data-jpa problems with entities weaved by EclipseLink

As soon as we use static weaving for our JPA entities with EclipseLink we get exceptions in our application.

The application is a web-application using spring-data-jpa and spring-data-rest-webmvc to offer CRUD functionality to change the entities.

When the entity classes are not processed by weaving, it works. But as soon as we use weaved entities, we get:

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]

This happens when the JSON response shall be marshalled containing an entity. The entity is plain simple, just 2 properties, no associations etc.

Here are the versions used:

<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>

Is there a configuration that supports this combination or a known issue that describes such a problem?

After some research I found a workaround, but maybe someone knows a better solution. The reason is, that the class JpaMetamodelMappingContext from spring-data-commons finds the variables in the entity classes, that were added by the weaving process. It collects them in its metamodel (JpaPersistentEntity) as properties, although these fields are only of interest for internal behavior of EclipseLink.

I replaced JpaMetamodelMappingContext with an own subclass so that those fields are not collected (like if they are @Transient). This was done with a HACK.

In our spring configuration we add a factory-class to the jpa:repositories

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

The new class EclipseJpaRepositoryFactoryBean introduces the new implementation of JpaMetamodelMappingContext. Here is the source: (Class copied from JpaRepositoryFactoryBean with change in 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();
}
}

The Hack that filters the internal properties is contained in class EclipseJpaMetamodelMappingContext (package must be 'org.springframework.data.jpa.mapping' to get access to class 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);
    }
}
}

This works for us, regardless of weaved or unweaved entities.

The problem does not occur when using Hibernate instead of EclipseLink, but using Hibernate would require the jackson-hibernate-module and other configuration changes.

I've fixed this by considering JPA @Transient annotated properties as transient now. So as long as your persistence provider correctly uses those you should be fine. If not, we'll have to file a ticket against it.

The fix will be available in 1.3.5 and 1.4 RC1 of Spring Data JPA.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM