Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running find throws a Detached entity passed to persist exception

I'm struggling with an error I get with Spring and Hibernate when trying to update a resource through a REST API.

I have simplified the case with minimal extra attributes.

Basic Model overview

I'm trying to update a resource called Rule.
Rule has a ThingGroup which is a representation of a group of objects. Rulehas also a set of Event which represents the activation ranges of the rule. During the execution of the application, the run will have to check some parameter on this thing group to trigger alerts or not.

Error thrown by Hibernate

My issue is that when using the update method in the rule service below, it fails with this error.

 org.hibernate.PersistentObjectException: detached entity passed to persist: com.smartobjectsecurity.common.domain.rule.Event
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:276)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:221)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417)
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.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.java:105)
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.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy117.findOne(Unknown Source)
at com.smartobjectsecurity.common.service.thing.ThingGroupServiceImpl.find(ThingGroupServiceImpl.java:62)
at com.smartobjectsecurity.common.service.thing.ThingGroupServiceImpl.find(ThingGroupServiceImpl.java:1)
at com.smartobjectsecurity.common.service.GenericServiceImpl.find(GenericServiceImpl.java:1)
at com.smartobjectsecurity.common.service.GenericServiceImpl$$FastClassBySpringCGLIB$$daaa7267.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:717)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
at com.smartobjectsecurity.common.service.thing.ThingGroupServiceImpl$$EnhancerBySpringCGLIB$$aa452fd7.find(<generated>)
at com.smartobjectsecurity.common.service.rule.RuleServiceImpl.update(RuleServiceImpl.java:219)

RuleService's update method

Below is the ruleService update method.
First I need to update the associated reaction group using the reactionGroup service.

@Transactional
public Rule update(final Rule rule, User user) throws UnknownEntityException, MyBadRequestException {
     final Long id = rule.getId();
     Rule found = null;
     try {
          found = find(id, user);
          found.setEvents(rule.getEvents());
          thingGroupService.find(rule.getThingGroup().getId(), user);
          found.setThingGroup(rule.getThingGroup());
          found = dao.saveAndFlush(found);

     } catch (final UnknownEntityException e) {
          final UnknownEntityException ex = new UnknownEntityException("UnknownEntity/ResourceException.rule", "update_unknown_rule");
          ex.setParameters(new Object[] { id });
          throw ex;
     }

     return found;
}

ThingGroupService's find method

 @Override
 @Transactional(rollbackFor = UnknownEntityException.class )
 public ThingGroup find(final Long id, User user) throws UnknownEntityException {
      logger.debug("-> find, id = " + id);
      final ThingGroup found = getDao().findOne(buildSpecificationForIdAndUser(id, user));
      if (found == null) {
           final UnknownEntityException ex = new UnknownEntityException("UnknownEntity/ResourceException.thingGroup", "unknown_thingGroup");
           ex.setParameters(new Object[] { id });
           throw ex;
      }
      logger.debug("<- find : " + found);
      return found;
 }

As requested here are the buildSpecificationForIdAndUser and buildSpecificationForUser methods.
They are used to build search restrictions based on the permissions of the users.

 @Transactional
protected Specification<ENTITY> buildSpecificationForIdAndUser(final ID id,final User user){
    return new Specification<ENTITY>() {
        @Override
        public Predicate toPredicate(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
            Expression<Long> entityId = root.get("id");


            Predicate userPredicate = buildSpecificationForUser(user).toPredicate(root, query, builder);

            return builder.and(
                builder.equal(entityId, id),
                userPredicate
            );
        }
    };
}

 @Override
@Transactional
protected Specification<ThingGroup> buildSpecificationForUser(final User user) {
    return new Specification<ThingGroup>() {
        @Override
        public Predicate toPredicate(Root<ThingGroup> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
            Expression<Collection<User>> managersOfThingGroup = root.get("managers");
            Expression<Company> thingGroupCompany = root.get("company");

            Predicate isManager = builder.isMember(user, managersOfThingGroup);
            Predicate isSameCompany = builder.equal(thingGroupCompany,user.getCompany());

            return builder.and(isSameCompany,isManager);

        }
    };
}

Where the error lies

When attempting to run thingGroupService.find(rule.getThingGroup().getId(), user); , Hibernate suddenly throws the exception about the Event entoty (detached entity passed to persist).
I really don't know what is wrong here.
I have been searching of various forums for several days without finding the cause of my issue.

Question

Why does the Event entity suddenly become detached from the session after running a find on a ThingGroup resource which has nothing to do whith event ?

like image 220
singe3 Avatar asked Aug 12 '15 11:08

singe3


People also ask

How do you resolve a detached entity passed to persist?

The solution is simple, just use the CascadeType. MERGE instead of CascadeType. PERSIST or CascadeType.

What is detached entity passed to persist?

A detached entity (a.k.a. a detached object) is an object that has the same ID as an entity in the persistence store but that is no longer part of a persistence context (the scope of an EntityManager session).

Which exception will be thrown by persist method?

Class PersistenceException Thrown by the persistence provider when a problem occurs.

How do you make a detached object persistence in JPA?

Making A Detached Object Persistent Again One way to achieve this is to use the update method of the session. The update method forces an update operation to be fired for the passed object. As this object is detached, Hibernate adds this object to the persistence context (making it persistent) and treats it as dirty.


2 Answers

You don't have to call saveAndFlush for an already attached entity, so the service method should be changed to:

found = find(id, user);
thingGroupService.find(rule.getThingGroup().getId(), user);
found.setThingGroup(rule.getThingGroup());
found.setEvents(rule.getEvents());

The found entity is already associated to the current Session so all changes are detected by the dirty checking mechanism and the child entity state transitions are propagated if cascading is enabled.

like image 192
Vlad Mihalcea Avatar answered Sep 22 '22 00:09

Vlad Mihalcea


I have managed to solve the issue.
However I dont't understand why it works now, I'm still investigating.
I just reversed two lines of code and Hibernate stop throwing the detached entity exception.

Now I have :

found.setEvents(rule.getEvents());
thingGroupService.find(rule.getThingGroup().getId(), user);

instead of :

thingGroupService.find(rule.getThingGroup().getId(), user);
found.setEvents(rule.getEvents());

Maybe Hibernate is automatically flushing at some point but I'm not sure why it has solved the issue.

like image 31
singe3 Avatar answered Sep 23 '22 00:09

singe3