spring data jpa多对多关系报 detached entity passed to persist错误

二次信任 提交于 2020-02-26 02:08:49

    最近使用spring data jpa维护表和表的多对多关系时,出现了一个奇怪的问题。当将一个new出来的对象放到实体类维护多对多关系的set中,然后使用jpa进行保存时,会出现如下异常。

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.infiai.webmessenger.dao.ProductTag; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.infiai.webmessenger.dao.ProductTag
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:299)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:488)
        at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
        at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
        at com.sun.proxy.$Proxy127.saveAndFlush(Unknown Source)
        at com.infiai.webmessenger.service.UserService.addUser(UserService.java:38)
        at com.infiai.webmessenger.task.UpdateM3OrderDataTask.updateM3OrderDataTask(UpdateM3OrderDataTask.java:128)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
        at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
        at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.infiai.webmessenger.dao.ProductTag
        at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124)
        at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:765)
        at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:758)
        at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80)
        at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:398)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162)
        at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:431)
        at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:363)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162)
        at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:111)
        at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:456)
        at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:278)
        at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:178)
        at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:109)
        at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
        at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
        at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
        at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
        at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:775)
        at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748)
        at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753)
        at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
        at com.sun.proxy.$Proxy116.persist(Unknown Source)
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:508)
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:522)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)

     实体类User和ProductTag之间使用jpa维护一个双向多对多关系如下所示

@Entity
@Table(name = "user",
indexes = {@Index(name="unique_index", columnList="name,psid", unique = true)})
public class User
{
	@Id
	@GeneratedValue
	private Integer id;
	private String name;
	private String psid;
	@ManyToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
	@JoinTable(name = "user_tag", 
				joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
				inverseJoinColumns = {@JoinColumn(name = "tag_id", referencedColumnName = "id")}
	)
	private Set<ProductTag> tagSet = new HashSet<ProductTag>();
    
    //get set方法省略

}

@Entity
@Table(name = "product_tag",
indexes = {@Index(name="unique_index", columnList="tagName", unique = true)})
public class ProductTag
{
	@Id
	@GeneratedValue
	private Integer id;
	private String tagName;
	@ManyToMany(mappedBy = "tagSet", fetch=FetchType.EAGER)
	private Set<User> userSet = new HashSet<User>();

    //get set方法省略
}

     使用如下方法向数据库插入数据是会报detached entity passed to persist错误

User user = userService.findByNameAndPsid(orderData.getClientName(), orderData.getPsid());
tag = new ProductTag();
tag.setTagName(data.getAsin());
user.getTagSet.add(tag);
userService.addUser(user);

    其中user对象是用jpa从数据库中查询出来的,所以为持久态。而tag为new出来的,是一个游离态的对象。

    将该游离态对象加入到user一端维护多对多关系的set集合中,再保存会粗线上述错误。

    后经分析,出现该错误的原因主要有两点:

    1.tag对象为一个游离态对象,需要首先将其加入数据库变为持久态,再加入user对象的tagSet中去。所以会报游离态的错误。

    2.由于实体类是一个双向的多对多关系,所以再保存时需要在两方, user方,productTag方都维护多对多的关系。所以在productTag的userSet集合中也需要加入user。

    经修改,如下代码成功保存

@Entity
@Table(name = "user",
indexes = {@Index(name="unique_index", columnList="name,psid", unique = true)})
public class User
{
	@Id
	@GeneratedValue
	private Integer id;
	private String name;
	private String psid;
	@ManyToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
	@JoinTable(name = "user_tag", 
				joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
				inverseJoinColumns = {@JoinColumn(name = "tag_id", referencedColumnName = "id")}
	)
	private Set<ProductTag> tagSet = new HashSet<ProductTag>();
	
	public void addTag(ProductTag tag, boolean setTag)
	{
		tagSet.add(tag);
		if(setTag)
		{
			tag.addUser(this, false);
		}
	}

    //忽略get set方法
}



@Entity
@Table(name = "product_tag",
indexes = {@Index(name="unique_index", columnList="tagName", unique = true)})
public class ProductTag
{
	@Id
	@GeneratedValue
	private Integer id;
	private String tagName;
	@ManyToMany(mappedBy = "tagSet", fetch=FetchType.EAGER)
	private Set<User> userSet = new HashSet<User>();
	
	public void addUser(User user, boolean setUser)
	{
		userSet.add(user);
		if(setUser)
		{
			user.addTag(this, false);
		}
	}
    //忽略get set方法

}

 

User user = userService.findByNameAndPsid(orderData.getClientName(), orderData.getPsid());
tag = new ProductTag();
tag.setTagName(data.getAsin());
//先将游离态对象保存
tag = tagService.addTag(tag);
user.addTag(tag, true);
userService.addUser(user);

    其中addUser方法维护了两端的多对多关系,addTag方法也是一样。boolean变量setUser,setTag是为了防止无限循环保存。

   最后总结:出现该问题的原因为,双向实体类关系,两端都要维护,保存多对多关系时,两方需要为持久态。

    有关实体类多对多关系与数据库一致性问题在如下链接有详细讨论

    https://stackoverflow.com/questions/13370221/jpa-hibernate-detached-entity-passed-to-persist

    https://notesonjava.wordpress.com/2008/11/03/managing-the-bidirectional-relationship/

    

 

 

 

 

 

 

 

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!