最近使用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/
来源:oschina
链接:https://my.oschina.net/daZhiWisdom/blog/3159540