2016-12-27 74 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

相關問題