保存对象时抛出JPA EntityExistsException的问题求助
问题:共享复合主键的实体保存时触发EntityExistsException
实体类定义
Model实体
@Entity @Immutable @Cacheable(true) @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) @Table(name = "Models") public class Model implements Serializable { private static final long serialVersionUID = 1L; @EmbeddedId private Key id; @Column(name = "ver_id") private String versionId; // etc. }
Graph实体
@Entity @Table(name = "Graphs") public class Graph implements Serializable { private static final long serialVersionUID = 1L; @EmbeddedId private Key id; @OneToOne(fetch = FetchType.EAGER) @JoinColumns({@JoinColumn(name = "PCG_ID", referencedColumnName = "MOD_ID"), @JoinColumn(name = "PCG_LANGUAGE", referencedColumnName = "MOD_LANGUAGE") }) @MapsId private Model model; // etc. }
复合主键类Key
@Embeddable public class Key implements Serializable { private static final long serialVersionUID = 1L; @Column(name = "MOD_ID") private String modelId; @Column(name = "MOD_LANGUAGE") private String language; // etc. }
业务执行代码
在同一事务中执行以下代码时触发异常:
TransactionStatus status = null; status = this.transactionManager.getTransaction(null); Model model = new Model(); model.setId(new Key("123", "EN")); // rest of Model setters this.modelRepository.save(model); // public interface ModelRepository extends JpaRepository<Model, Key> Graph graph = new Graph(); graph.setModel(model); // rest of Graph setters this.graphRepository.save(graph); // public interface GraphRepository extends JpaRepository<Graph, Key> this.transactionManager.commit(status); // Commit is never actually reached. Exception is thrown before
错误信息
org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session : [com.company.app.model.app.Model#Key [id=123, language=EN]]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.company.app.model.app.Model#Key [id=123, language=EN]] at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:400) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:235) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551) at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:241) at com.sun.proxy.$Proxy90.save(Unknown Source) at com.company.app.dataaccess.service.GraphService.insert(GraphService.java:29) at com.company.app.appNPExporter.createGraphData(appNPExporter.java:776) at com.company.app.appNPExporter.handleModel(appNPExporter.java:633) at com.company.app.appNPExporter.handleChangedModel(appNPExporter.java:559) at com.company.app.appNPExporter.handleChangedModels(appNPExporter.java:507) at com.company.app.appNPExporter.execute(appNPExporter.java:340) at com.company.app.appExporterLauncher.executeCobaExport(appExporterLauncher.java:148) at com.company.app.appExporterLauncher.main(appExporterLauncher.java:67) Caused by: javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.company.app.model.app.Model#Key [id=123, language=EN]] at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:123) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:786) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748) at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:298) at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:510) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:434) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220) at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:153) at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:426) at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:263) at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:192) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:134) at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55) at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:756) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:742) at jdk.internal.reflect.GeneratedMethodAccessor86.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:315) at com.sun.proxy.$Proxy84.persist(Unknown Source) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:666) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289) at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137) at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121) at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:530) at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:286) at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:640) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:139) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:76) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ... 15 common frames omitted
问题核心
已通过JpaRepository保存的Model实例被Graph对象引用时,抛出了EntityExistsException,但Graph并未配置级联持久化,为何会出现此问题?
原因分析
- @Immutable实体的特殊处理:Model标注了
@Immutable,Hibernate在保存这类实体时,不会将原始对象纳入Session管理,而是创建一个新的托管实例返回。 - 对象状态冲突:代码中保存Model后,仍使用原始的非托管Model对象设置给Graph。此时Session中已经存在一个与原始对象ID相同的托管Model实例(save方法返回的实例)。
- @MapsId触发的隐式校验:Graph的
@OneToOne关联使用了@MapsId,Hibernate在保存Graph时会自动校验关联实体的状态。当发现关联的Model是非托管状态时,会尝试执行persist操作,此时Session中已有同ID的托管实例,因此抛出"不同对象拥有相同标识符已关联到Session"的异常。
解决方案
方案1:使用save返回的托管实例(推荐)
保存Model后,将返回的托管实例赋值给原变量,再关联到Graph:
TransactionStatus status = null; status = this.transactionManager.getTransaction(null); Model model = new Model(); model.setId(new Key("123", "EN")); // rest of Model setters model = this.modelRepository.save(model); // 接收返回的托管实例 Graph graph = new Graph(); graph.setModel(model); // rest of Graph setters this.graphRepository.save(graph); this.transactionManager.commit(status);
方案2:配置级联合并
在Graph的@OneToOne注解中添加cascade = CascadeType.MERGE,让Hibernate在保存Graph时合并关联的Model实例而非重新持久化:
@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.MERGE) @JoinColumns({@JoinColumn(name = "PCG_ID", referencedColumnName = "MOD_ID"), @JoinColumn(name = "PCG_LANGUAGE", referencedColumnName = "MOD_LANGUAGE") }) @MapsId private Model model;
内容的提问来源于stack exchange,提问作者Martin819
相关产品推荐
相关产品推荐

