My Parent
entity has a Child
entity and collection of Element
s:
@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;
}
}
The Child
entity also has a collection of Element
s, but it is restricted to contain only elements from the Parent
's Element
s collection. (but not necessarily all of them).
@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;
}
}
Here is the Element
entity:
@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);
}
}
I want to pass an instance of Parent
to save()
method, so that the top level Element
collection is created automatically (this is why I add cascade option to the first one). Parent
's Child
instance should also be saved, but it's elements should not be created, rather it should create reference to those that already exist in the top level collection (so no cascade in Child
).
However, the following code produces exception (Spring's JPARepository
internally calls 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);
Stack trace:
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
The way I understand it is that Hibernate first processes the child (before the top level elements collection) and it fails to find Element instances that should be associated with the Child instance, because they are created when persisting the top-level elements collection.
How is this problem solved in Hibernate/JPA? Can I instruct it to persist my structure the way I want without falling back to manual field-by-field saving?