gpt4 book ai didi

spring - 如何在同一个 Spring Data JPA 保存请求中级联创建和存储引用?

转载 作者:行者123 更新时间:2023-12-01 12:24:34 24 4
gpt4 key购买 nike

我的 Parent实体有一个 Child实体和集合 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;
}

}
Child实体还有一个集合 Element s,但仅限于包含来自 Parent 的元素的 Element收藏。 (但不一定是全部)。
@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

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

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

最佳答案

您当前的问题源于您设置了没有版本属性的实体的 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);

    关于您的假设的更多评论

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


  • 您必须创建集合,JPA 不会为您做这件事(但这似乎只是措辞问题,因为您在代码中做得很好)
  • 级联选项几乎与为实体创建的记录无关,而仅与何时创建有关。与 CascadeType.ALL当手头的实体被保存时,引用的实体将被保存。如果没有级联,您将不得不自己保存它。如果你不这样做,JPA 会抛出一个关于非持久化实体的异常

  • 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).


  • 同样,级联与创建什么数据库记录无关,只与谁负责。
  • 如果您希望 JPA 中的事物引用相同的第三个事物,只需使用第三个事物的相同实例。 JPA 会弄清楚的。

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



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

    关于spring - 如何在同一个 Spring Data JPA 保存请求中级联创建和存储引用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41344032/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com