Запуск find выбрасывает выделенную сущность, переданную для сохранения исключения
Я борюсь с ошибкой, которую получаю с помощью Spring и Hibernate при попытке обновить ресурс с помощью REST API.
Я упростил случай с минимальными дополнительными атрибутами.
Обзор базовой модели
Я пытаюсь обновить ресурс с именем Rule
.
Rule
имеет ThingGroup
, представляющий собой группу объектов.
Rule
также имеет набор Event
, который представляет диапазоны активации правила.
Во время выполнения приложения, запуск должен будет проверить некоторый параметр в этой группе объектов для запуска предупреждений или нет.
Ошибка, сгенерированная Hibernate
Моя проблема заключается в том, что при использовании метода update
в приведенной ниже таблице правил это происходит с ошибкой.
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
Ниже приведен метод ruleService update
.
Сначала мне нужно обновить связанную группу реагирования, используя службу groupGroup.
@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
@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;
}
В соответствии с запрошенными здесь методами buildSpecificationForIdAndUser
и buildSpecificationForUser
.
Они используются для создания ограничений поиска на основе разрешений пользователей.
@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);
}
};
}
Где ошибка лежит
При попытке запустить thingGroupService.find(rule.getThingGroup().getId(), user);
, Hibernate неожиданно выдает исключение из Entoty события (удаленный объект передан для сохранения).
Я действительно не знаю, что здесь не так.
Я несколько раз искал различные форумы, не найдя причины моей проблемы.
Вопрос
Почему объект Event
внезапно отключается от сеанса после запуска поиска на ресурсе ThingGroup
, который не имеет ничего общего с событием?
Ответы
Ответ 1
Мне удалось решить проблему.
Однако я не понимаю, почему это работает сейчас, я все еще расследую.
Я просто перевернул две строки кода и Hibernate прекратил бросать исключение отдельного объекта.
Теперь у меня есть:
found.setEvents(rule.getEvents());
thingGroupService.find(rule.getThingGroup().getId(), user);
вместо:
thingGroupService.find(rule.getThingGroup().getId(), user);
found.setEvents(rule.getEvents());
Может быть, Hibernate автоматически покраснет в какой-то момент, но я не уверен, почему он решил проблему.
Ответ 2
Вам не нужно вызывать saveAndFlush
для уже прикрепленного объекта, поэтому метод службы следует изменить на:
found = find(id, user);
thingGroupService.find(rule.getThingGroup().getId(), user);
found.setThingGroup(rule.getThingGroup());
found.setEvents(rule.getEvents());
Объект found
уже связан с текущим Session
, поэтому все изменения обнаруживаются с помощью грязного механизма проверки и дочернего переходы состояния объекта распространяются, если включено каскадное преобразование.
Ответ 3
Вот почему я попросил реализовать методы buildSpecification
. Я хотел проверить, выполняется ли запрос или методы Session
get
/load
для получения объекта по идентификатору.
В принципе, если режим флеша AUTO
(по умолчанию) Hibernate должен иногда скрывать сеанс перед выполнением запроса, чтобы избежать запроса устаревших данных. Javadoc для FlushMode.AUTO:
Session
иногда очищается до выполнения запроса, чтобы убедитесь, что запросы никогда не возвращают устаревшее состояние. Это значение по умолчанию режим флеша.
Если у вас есть отдельные объекты, на которые ссылаются управляемые, вы получите исключение.
Решение состоит в том, чтобы правильно привязать объекты до выполнения запроса или изменить режим очистки для затронутой транзакции на COMMIT
:
entityManager.setFlushMode(FlushMode.COMMIT)