2016-12-27 78 views
2

Parent实体拥有的ElementChild实体和集合:如何在同一个Spring Data JPA保存请求中级联创建和存储引用?

@Entity 
public class Parent { 
    @Id 
    private String id; 

    @OneToOne(cascade = CascadeType.ALL) 
    private Child child; 

    @ManyToMany(cascade = CascadeType.ALL) 
    private List<Element> elements; 

    public String getId() { 
     return id; 
    } 

    public void setId(String id) { 
     this.id = id; 
    } 

    public Child getChild() { 
     return child; 
    } 

    public void setChild(Child child) { 
     this.child = child; 
    } 

    public List<Element> getElements() { 
     return elements; 
    } 

    public void setElements(List<Element> elements) { 
     this.elements = elements; 
    } 

} 

Child实体也有Element个集合,但它仅限于包含来自ParentElement的藏品只有元素。 (但不一定全部)。

@Entity 
public class Child { 
    @Id 
    private String id; 

    @ManyToMany 
    private List<Element> elements; 

    public String getId() { 
     return id; 
    } 

    public void setId(String id) { 
     this.id = id; 
    } 

    public List<Element> getElements() { 
     return elements; 
    } 

    public void setElements(List<Element> elements) { 
     this.elements = elements; 
    } 
} 

这里是Element实体:

@Entity 
public class Element { 

    public Element() {} 

    public Element(String id) { 
     this.id = id; 
    } 

    @Id 
    private String id; 

    public String getId() { 
     return id; 
    } 

    public void setId(String id) { 
     this.id = id; 
    } 

    @Override 
    public boolean equals(Object other) { 
     if (!(other instanceof Element)) { 
      return false; 
     } 
     return id.equals(((Element) other).getId()); 
    } 

    @Override 
    public int hashCode() { 
     return Objects.hashCode(id); 
    } 
} 

我想的Parent一个实例传递给save()方法,从而自动创建的顶级Element集合(这就是为什么我添加级联选项到第一个)。 ParentChild实例也应该保存,但它的元素不应该创建,而应该创建对顶级集合中已存在的元素的引用(因此Child中没有级联)。

但是,下面的代码产生异常(Spring的JPARepository内部调用EntityManager.merge()

Parent parent = new Parent(); 
    parent.setId("someId"); 

    Element element1 = new Element("id1"); 
    Element element2 = new Element("id2"); 
    Element element3 = new Element("id3"); 

    Element element1Copy = new Element("id1"); 
    Element element3Copy = new Element("id3"); 

    List<Element> originalElements = new ArrayList<Element>(); 
    originalElements.add(element1); 
    originalElements.add(element2); 
    originalElements.add(element3); 
    parent.setElements(originalElements); 

    Child child = new Child(); 
    child.setId("childId"); 
    parent.setChild(child); 
    List<Element> elementsCopies = new ArrayList<Element>(); 
    elementsCopies.add(element1Copy); 
    elementsCopies.add(element3Copy); 
    child.setElements(elementsCopies); 


    Parent saved = parentRepository.save(parent); 

堆栈跟踪:

Caused by: org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find org.daniel.model.Element with id id1; nested exception is javax.persistence.EntityNotFoundException: Unable to find org.daniel.model.Element with id id1 
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:389) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:491) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) ~[spring-data-jpa-1.10.5.RELEASE.jar:na] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at com.sun.proxy.$Proxy73.save(Unknown Source) ~[na:na] 
    at org.daniel.Monitor.createAndSave(Monitor.java:47) ~[classes/:na] 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_40] 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_40] 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_40] 
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_40] 
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:366) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:311) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:134) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    ... 23 common frames omitted 
Caused by: javax.persistence.EntityNotFoundException: Unable to find org.daniel.model.Element with id id1 
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:144) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1129) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1022) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:639) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.type.EntityType.resolve(EntityType.java:431) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.type.EntityType.replace(EntityType.java:330) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:518) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.type.CollectionType.replace(CollectionType.java:663) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.type.AbstractType.replace(AbstractType.java:147) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.type.TypeHelper.replaceAssociations(TypeHelper.java:261) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:427) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:240) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:301) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:170) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:850) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:832) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:260) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:398) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:111) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:425) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:232) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:301) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:170) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:69) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:840) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:822) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:827) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:1161) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final] 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_40] 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_40] 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_40] 
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_40] 
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at com.sun.proxy.$Proxy71.merge(Unknown Source) ~[na:na] 
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:509) ~[spring-data-jpa-1.10.5.RELEASE.jar:na] 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_40] 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_40] 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_40] 
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_40] 
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:503) ~[spring-data-commons-1.12.5.RELEASE.jar:na] 
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:488) ~[spring-data-commons-1.12.5.RELEASE.jar:na] 
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:460) ~[spring-data-commons-1.12.5.RELEASE.jar:na] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61) ~[spring-data-commons-1.12.5.RELEASE.jar:na] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE] 
    ... 38 common frames omitted 

我的理解是,休眠首先处理子(之前的方式顶级元素集合),并且它无法找到应该与Child实例关联的Element实例,因为它们是在持久化顶级元素集合时创建的。

这个问题在Hibernate/JPA中如何解决?我可以指示它以我想要的方式坚持我的结构,而不会退回到手动逐场保存吗?

+0

我会重新考虑equals和hashcode的实现。请参阅http://stackoverflow.com/a/4388453/66686 –

回答

3

你目前的问题源于你设置了实体的id,它没有版本属性。因此,Spring认为这是一个已经存在的实体,并试图在当前的EntityManager中合并它。但实际上这是一个新例子。

所以为了解决这个问题,您可以执行下列操作之一:

  • 属性与@Version

  • 添加到您的实体和注释它让JPA生成的ID

我推荐第一种方法,因为版本属性由于其他原因也是有用的。

但是,您的问题不会停在那里:您正在创建具有相同类型和ID的多个实体,只要您解决了其他问题,就会引发异常。

而是只使用同一个实例,像这样:

我想通过家长的实例保存()方法

Parent parent = new Parent(); 
    parent.setId("someId"); 

    Element element1 = new Element("id1"); 
    Element element2 = new Element("id2"); 
    Element element3 = new Element("id3"); 

    List<Element> originalElements = new ArrayList<Element>(); 
    originalElements.add(element1); 
    originalElements.add(element2); 
    originalElements.add(element3); 
    parent.setElements(originalElements); 

    Child child = new Child(); 
    child.setId("childId"); 
    parent.setChild(child); 
    List<Element> elementsCopies = new ArrayList<Element>(); 
    elementsCopies.add(element1); 
    elementsCopies.add(element3); 
    child.setElements(elementsCopies); 


    Parent saved = parentRepository.save(parent); 

对你的假设有些更多评论,因此顶级元素集合是自动创建的(这就是为什么我在第一个元素上添加级联选项的原因)。

  1. 你必须创建集合,JPA不会为你做的(但似乎刚要措辞的问题,因为你这样做,只是在你的代码罚款)

  2. 级联选项与为实体创建的记录几乎完全没有关系,但仅限于何时。随着CascadeType.ALL参考实体将得到保存,当手头的实体得到保存。没有级联,你将不得不自己保存。如果你不这样做,JPA将BARF了一个异常有关不保持实体

父项的子实例也应保存,但它的内容不应该被创建的,而是应该创建参照这些已经存在于顶级集合中(所以在Child中没有级联)。

  1. 再次级联无关用GET只有谁负责它创造了什么数据库记录。

  2. 如果你想要JPA中的东西指向相同的第三件事,只需使用第三件事物的相同实例。 JPA将解决它。

我的理解是,休眠首先处理孩子(顶级元素集合之前),也未能找到应该与孩子实例相关联Element实例,因为他们被创建时的方式坚持顶级元素集合。

如上所述,这不是问题所在。一旦您向JPA提供了您想要保存的实例图形,它会找出正确的顺序(几乎在所有情况下)。

+0

在实际场景中,我将通过REST接收对象并反序列化它们,因此我将无法控制实例创建 - 具有相同ID的实体将是不同的对象实例。这就是为什么我重写equals(和hashCode) - 因为我认为这样我会迫使Hibernate尊重基于ID的身份(而不是基于实例) – Danio

+0

不,Hibernate将始终使用对象身份internaly。那么,如果你有多个具有相同ID的分离实例,Spring/Hibernate会将它们合并,产生一个实例,它将具有最后一个合并实例的属性值。你是否有那些元素的属性在Rest请求中的所有地方都重复?听起来很奇怪。无论如何,如果你想输入如何正确处理,那将是不同的问题。 –

+0

好的,所以结论是,我必须将相同的对象(在相同的id中)反序列化到相同的实例。 – Danio

相关问题