Tag Archives: spring data jpa

[Solved] Spring data JPA many to many relationship report detached entity passed to persist error

Recently, when using spring data JPA to maintain many to many relationships between tables, there is a strange problem. When a new object is put into a set of entity classes to maintain many to many relationships, and then JPA is used to save it, the following exception will appear

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)

JPA is used to maintain a bidirectional many to many relationship between the entity class user and producttag, as shown below

@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>();
    

}

@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>();
}

Using the following method to insert data into the database will report a detached entity passed to persist error

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

The user object is queried from the database with JPA, so it is persistent. And tag is new, it is a free state object

Adding the free state object to the set set set that maintains the many to many relationship at the user end, and then saving it, the above error will be highlighted

After analysis, there are two main reasons for the error

1. The tag object is a free state object, which needs to be added to the database to become persistent, and then added to the tagset of the user object. So it will report the error of free state

2. Because the entity class is a bi-directional many to many relationship, it is necessary to maintain the many to many relationship between the user and the producttag. Therefore, users need to be added to the userset collection of producttag

After modification, the following code is saved successfully

@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);
		}
	}
}



@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);
		}
	}

}
User user = userService.findByNameAndPsid(orderData.getClientName(), orderData.getPsid());
tag = new ProductTag();
tag.setTagName(data.getAsin());
//Save the free state object first
tag = tagService.addTag(tag);
user.addTag(tag, true);
userService.addUser(user);

The addUser method maintains the many to many relationship between the two ends, and so does the addtag method. Boolean variables setuser and settag are used to prevent infinite loop saving

Conclusion: the reason for this problem is that two-way entity class relationship should be maintained at both ends. When saving many to many relationship, two parties need to be persistent

Many to many relationships of entity classes and database consistency are discussed in detail in the following links

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

MANAGING THE BIDIRECTIONAL RELATIONSHIP