spring-data-jpa icon indicating copy to clipboard operation
spring-data-jpa copied to clipboard

Cannot create a repository for an entity with a shared key [DATAJPA-649]

Open spring-projects-issues opened this issue 11 years ago • 9 comments

Mauro Molinari opened DATAJPA-649 and commented

I have an entity:

@Entity
public class EntityA {
  @Id 
  private Long id;
  // getters/setters omitted
}

and another one with a shared primary key:

@Entity
public class EntityB {
  @Id
  @OneToOne
  private EntityA entityA;
  // getter/setters omitted
}

I'm trying to define a repository for EntityB:

public interface EntityBRepository extends JpaRepository<EntityB, EntityA> {
}

Apart from the fact that in order for this to be correct, EntityA must be serializable, even if it is I get the following error at runtime when the application starts (note I'm using EclipseLink 2.5.2):

Caused by: java.lang.IllegalArgumentException: Expected id attribute type [class java.lang.Long] on the existing id attribute [SingularAttributeImpl[EntityTypeImpl@1077297176:EntityA [ javaType: class com.example.EntityA descriptor: RelationalDescriptor(com.example.EntityA --> [DatabaseTable(EntityA)]), mappings: 9],org.eclipse.persistence.mappings.OneToOneMapping[entityA]]] on the identifiable type [EntityTypeImpl@512803841:EntityB [ javaType: class com.example.EntityB descriptor: RelationalDescriptor(com.example.EntityB --> [DatabaseTable(entityB)]), mappings: 7]] but found attribute type [class com.example.EntityA].
	at org.eclipse.persistence.internal.jpa.metamodel.IdentifiableTypeImpl.getId(IdentifiableTypeImpl.java:200) ~[org.eclipse.persistence.jpa-2.5.2.jar:?]
	at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdMetadata.<init>(JpaMetamodelEntityInformation.java:222) ~[spring-data-jpa-1.7.1.RELEASE.jar:?]
	at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:79) ~[spring-data-jpa-1.7.1.RELEASE.jar:?]
	at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getMetadata(JpaEntityInformationSupport.java:65) ~[spring-data-jpa-1.7.1.RELEASE.jar:?]
	at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:145) ~[spring-data-jpa-1.7.1.RELEASE.jar:?]
	at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:89) ~[spring-data-jpa-1.7.1.RELEASE.jar:?]
	at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:69) ~[spring-data-jpa-1.7.1.RELEASE.jar:?]
	at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:177) ~[spring-data-commons-1.9.1.RELEASE.jar:?]
	at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:239) ~[spring-data-commons-1.9.1.RELEASE.jar:?]
	at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:225) ~[spring-data-commons-1.9.1.RELEASE.jar:?]
	at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:92) ~[spring-data-jpa-1.7.1.RELEASE.jar:?]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1627) ~[spring-beans-4.1.2.RELEASE.jar:4.1.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1564) ~[spring-beans-4.1.2.RELEASE.jar:4.1.2.RELEASE]
	... 21 more

I tried to change the repository definition as such:

public interface EntityBRepository extends JpaRepository<EntityB, Long> {
}

and change the code that invokes findOne(ID) on that repository as such:

// instead of: entityBRepository.findOne(entityAInstance);
entityBRepository.findOne(entityAInstance.getId());

but the same error is produced when the repository gets initialized.

I then tried to change EntityB definition as such:

@Entity
public class EntityB {
  @Id
  private Long id;

  @MapsId
  @OneToOne
  private EntityA entityA;
  // getter/setters omitted
}

while keeping Long as the ID type in EntityBRepository definition, but still the same error is produced. So, I can't produce a repository for EntityB in any way :-(


Affects: 1.7.1 (Evans SR1)

13 votes, 15 watchers

spring-projects-issues avatar Dec 24 '14 05:12 spring-projects-issues

William Gorder commented

This is annoying the exact use case of

@Entity
public class EntityB {
  @Id
  private Long id;
 
  @MapsId
  @OneToOne
  private EntityA entityA;
  // getter/setters omitted
}

Is listed right on the @OneToOne Javadoc and Spring data JPA does not support it? https://docs.oracle.com/javaee/6/api/javax/persistence/OneToOne.html

spring-projects-issues avatar Aug 04 '15 04:08 spring-projects-issues

Mauro Molinari commented

Another similar problem is when you have a compound primary key made of a basic attribute and a related entity, although using a proper id class:

@Entity
public class Foo {
  @Id
  private Long id;
}

@Entity
@IdClass(BarId.class)
public class Bar {

  public static class BarId implements Serializable {
    private Long foo;
    private MyEnum type;
    protected BarId() {}
    public BarId(Long foo, MyEnum type) {
      this.foo = foo; this.type = type;
    }
    // getters omitted
  }

  @Id
  @ManyToOne
  private Foo foo;

  @Id
  @Enumerated(EnumType.STRING)
  private MyEnum type; 
}

The following repository, which I would expect to be declared correctly, produces an error:

public interface BarRepository
		extends JpaRepository<Bar, BarId> {
}

The error given on startup is:

java.lang.IllegalArgumentException: Expected id attribute type [class com.example.Bar$BarId] on the existing id attribute [SingularAttributeImpl[BasicTypeImpl@1836242833:MyEnum [ javaType: class com.example.Bar$MyEnum],org.eclipse.persistence.mappings.DirectToFieldMapping[type-->Bar.type]]] on the identifiable type [EntityTypeImpl@930128389:Bar [ javaType: class com.example.Bar descriptor: RelationalDescriptor(com.example.Bar --> [DatabaseTable(Bar)]), mappings: 9]] but found attribute type [class com.example.Bar$MyEnum].
    at org.eclipse.persistence.internal.jpa.metamodel.IdentifiableTypeImpl.getId(IdentifiableTypeImpl.java:200) ~[IdentifiableTypeImpl.class:?]
    at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdMetadata.<init>(JpaMetamodelEntityInformation.java:223) ~[JpaMetamodelEntityInformation$IdMetadata.class:?]
    at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:79) ~[JpaMetamodelEntityInformation.class:?]
    at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getEntityInformation(JpaEntityInformationSupport.java:67) ~[JpaEntityInformationSupport.class:?]
    at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:146) ~[JpaRepositoryFactory.class:?]
    at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:90) ~[JpaRepositoryFactory.class:?]
    at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:70) ~[JpaRepositoryFactory.class:?]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:171) ~[RepositoryFactorySupport.class:?]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:239) ~[RepositoryFactoryBeanSupport.class:?]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:225) ~[RepositoryFactoryBeanSupport.class:?]
    at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:92) ~[JpaRepositoryFactoryBean.class:?]

So, it seems like only very-basic primary key types are supported to create JPA repositories, which is a strong limit :-( Is there any plan to improve this? (I tested this with Spring Data JPA 1.8.2 and 1.9.1)

spring-projects-issues avatar Dec 16 '15 15:12 spring-projects-issues

Elwayusei commented

I have the same problem, Could you please help me? thnks

spring-projects-issues avatar Feb 29 '16 22:02 spring-projects-issues

Mario Ceste commented

I was able to work around the problem. Below is the mapping that worked using Eclipselink v2.5.0. I used a listener to set the identifier.

@Entity
class Bar {

  @Id
  @Column(name="id")
  private String id;

  @ManyToOne
  @PrimaryKeyJoinColumn
  private Foo foo;

  @PrePersist
  public void prePersist() {
    this.id = foo.id;
  }
}

Unfortunately Foo must be persisted before Bar for this to work

spring-projects-issues avatar Apr 13 '16 14:04 spring-projects-issues

Mark Paluch commented

I took a look at the issue and ran into following results by using the code from the original issue description:

  1. Hibernate: Requires the using class to implement Persistable<EntityA>, runs fine afterwards. Persistable is not implemented then save of the entity runs into a NullPointerException at
java.lang.NullPointerException
	at org.springframework.data.repository.core.support.AbstractEntityInformation.isNew(AbstractEntityInformation.java:54)
	at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.isNew(JpaMetamodelEntityInformation.java:223)
	at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:505)
  1. EclipseLink: same issue with Persistable then the exception from above.
  2. OpenJPA: Without Persistable
<openjpa-2.4.1-r422266:1730418 nonfatal user error> org.apache.openjpa.persistence.ArgumentException: Encountered new object in persistent field "org.springframework.data.jpa.repository. EntityB.entityA" during attach.  However, this field does not allow cascade attach. Set the cascade attribute for this field to CascadeType.MERGE or CascadeType.ALL (JPA annotations) or "merge" or "all" (JPA orm.xml). You cannot attach a reference to a new object without cascading.
FailedObject: org.springframework.data.jpa.repository.EntityA@1de9d54

with Persistable it runs fine

spring-projects-issues avatar Jun 29 '16 12:06 spring-projects-issues

Mark Paluch commented

Using a shared entity as @Id isn't a good idea, the better approach is sticking to the way the JPA spec describes.

@Entity
public class EntityB {
  @Id
  private Long id;
 
  @MapsId
  @OneToOne
  private EntityA entityA;
}

This approach works with Hibernate and OpenJPA but fails with EclipseLink. The repository must declare … extends JpaRepository<EntityB, Long>. I filed a bug at the EclipseLink Bugzilla and created a test-case.

Bug report: https://bugs.eclipse.org/bugs/show_bug.cgi?id=497143 Test case: https://gist.github.com/mp911de/ba6b5150a486da42a65f8efecf86f70c

spring-projects-issues avatar Jul 01 '16 11:07 spring-projects-issues

Mauro Molinari commented

I think I never received notifications for this bug, this is why I reply only now.

Why using a shared primary key should not be a good idea? It's listed in Pro JPA 2 as a legitimate use case. Indeed, the use of @MapsId is a "plus" when you also want to reference the Long primary key. Plain EclipseLink works perfectly fine in this scenario and I'm using it with now issue at all (apart from this problem with Spring Data).

It's not clear to me why should implementing Persistable necessary? Is this a sort of proposed workaround?

spring-projects-issues avatar Nov 02 '18 16:11 spring-projects-issues

jonlondonwork commented

Did anyone find a workaround for this problem?

I have encountered the same issue with eclipselink 2.6.1 and spring-data-jpa 2.1.4.RELEASE

spring-projects-issues avatar Jun 10 '20 18:06 spring-projects-issues

The issue still lingers on EclipseLink's bug tracker.

gregturn avatar Apr 19 '22 21:04 gregturn