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.
I'm trying to update a resource called Rule
.Rule
has a ThingGroup
which is a representation of a group of objects.
Rule
has 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.
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)
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;
}
@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);
}
};
}
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.
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 ?
The solution is simple, just use the CascadeType. MERGE instead of CascadeType. PERSIST or CascadeType.
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).
Class PersistenceException Thrown by the persistence provider when a problem occurs.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With