JPA считает, что я удаляю отдельный объект
У меня есть DAO, который я использовал для загрузки и сохранения объектов домена с помощью JPA. Наконец-то мне удалось заставить работать транзакцию, теперь у меня другая проблема.
В моем тестовом примере я вызываю свой DAO для загрузки объекта домена с заданным идентификатором, проверяю, что он загружен, а затем вызывает тот же DAO, чтобы удалить только что загруженный объект. Когда я это сделаю, я получаю следующее:
java.lang.IllegalArgumentException: Removing a detached instance mil.navy.ndms.conops.common.model.impl.jpa.Group#10
at org.hibernate.ejb.event.EJB3DeleteEventListener.performDetachedEntityDeletionCheck(EJB3DeleteEventListener.java:45)
at org.hibernate.event.def.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:108)
at org.hibernate.event.def.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:74)
at org.hibernate.impl.SessionImpl.fireDelete(SessionImpl.java:794)
at org.hibernate.impl.SessionImpl.delete(SessionImpl.java:772)
at org.hibernate.ejb.AbstractEntityManagerImpl.remove(AbstractEntityManagerImpl.java:253)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:48)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
at java.lang.reflect.Method.invoke(Method.java:600)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:180)
at $Proxy27.remove(Unknown Source)
at mil.navy.ndms.conops.common.dao.impl.jpa.GroupDao.delete(GroupDao.java:499)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:48)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
at java.lang.reflect.Method.invoke(Method.java:600)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:304)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy28.delete(Unknown Source)
at mil.navy.ndms.conops.common.dao.impl.jpa.GroupDaoTest.testGroupDaoSave(GroupDaoTest.java:89)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:48)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
at java.lang.reflect.Method.invoke(Method.java:600)
at junit.framework.TestCase.runTest(TestCase.java:164)
at junit.framework.TestCase.runBare(TestCase.java:130)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:120)
at junit.framework.TestSuite.runTest(TestSuite.java:230)
at junit.framework.TestSuite.run(TestSuite.java:225)
at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Теперь, учитывая, что я использую тот же экземпляр DAO, и я не изменил EntityManagers (если только Spring делает это, не сообщая мне об этом), как это может быть отдельный файл?
Мой код DAO выглядит следующим образом:
public class GenericJPADao<INTFC extends IAddressable, VO extends BaseAddressable> implements IWebDao, IDao<INTFC>, IDaoUtil<INTFC>
{
private static Logger logger = Logger.getLogger (GenericJPADao.class);
protected Class<?> voClass;
@PersistenceContext(unitName = "CONOPS_PU")
protected EntityManagerFactory emf;
@PersistenceContext(unitName = "CONOPS_PU")
protected EntityManager em;
public GenericJPADao()
{
super ( );
ParameterizedType genericSuperclass =
(ParameterizedType) getClass ( ).getGenericSuperclass ( );
this.voClass = (Class<?>) genericSuperclass.getActualTypeArguments ( )[1];
}
...
public void delete (INTFC modelObj, EntityManager em)
{
em.remove (modelObj);
}
@SuppressWarnings("unchecked")
public INTFC findById (Long id)
{
return ((INTFC) em.find (voClass, id));
}
}
Код тестового кода выглядит так:
IGroup loadedGroup = dao.findById (group.getId ( ));
assertNotNull (loadedGroup);
assertEquals (group.getId ( ), loadedGroup.getId ( ));
dao.delete (loadedGroup); // - This generates the above exception
loadedGroup = dao.findById (group.getId ( ));
assertNull(loadedGroup);
Может ли кто-нибудь сказать мне, что я здесь делаю неправильно?
Ответы
Ответ 1
Я подозреваю, что вы выполняете свой код за пределами транзакции, поэтому ваши операции find
и delete
встречаются в отдельном контексте персистентности, а find
фактически возвращает экземпляр отсоединенный (поэтому JPA и вы ARE удаляете отдельный объект).
Оберните последовательность поиска/удаления внутри транзакции.
Обновление: Ниже выдержки из главы 7.3.1. Контекст сохранения транзакций:
Если вы используете EntityManager
с моделью контекста персистентности транзакций за пределами активной транзакции, каждый вызов метода создает новый контекст персистентности, выполняет действие метода и завершает контекст персистентности. Например, рассмотрите возможность использования метода EntityManager.find
вне транзакции. EntityManager
создаст временный контекст персистентности, выполнит операцию поиска, завершит контекст персистентности и вернет вам выделенный объект результата. Второй вызов с тем же идентификатором вернет второй отдельный объект.
Ответ 2
public void remove(Object obj){
em.remove(em.merge(obj));
}
Вышеприведенный код аналогичен предложенному zawhtut
Ответ 3
+1 к сообщению Pascal Thivent и просто следуйте за ним.
@Transactional
public void remove(long purchaseId){
Purchase attached = jpaTemplate.find(Purchase.class,purchaseId);
jpaTemplate.remove(attached);
}
Ответ 4
Получить экземпляр с помощью em.getReference()
вместо em.find()
.
Например, попробуйте:
em.remove(em.getReference(INTFC.class, id));
Ответ 5
Вот что я использовал (на основе предыдущих ответов)
public void deleteTask(int taskId) {
Task task = getTask(taskId); //this is a function that returns a task by id
if (task == null) {
return;
}
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
em.remove(em.merge(task));
et.commit();
em.close();
}
Ответ 6
Транзакция обеспечивает свойства ACID, но не является ли объект прикреплен или отсоединен.
Даже если вы используете entityManager.find
и entityManager.remove()
в той же транзакции, нет гарантии, что объект будет прикреплен. Поэтому перед выпуском entityManager.remove()
проверьте, прикреплен ли объект, если он не присоединяется к нему с помощью enitityManger.merge(entity)
, а затем выдает entityManager.remove
на нем следующим образом:
@Transactional
public void delete (long id)
{
ModelObj modelObj=entityManager.find(ModelObj.class,id);
modelObj=entityManager.contains(modelObj)?modelObj:entityManager.merge(modelObj);
em.remove (modelObj);
}
Ответ 7
Для меня сработал вызов flush, поэтому изменения были сделаны, как в следующем примере:
@PersistanceContext
EntityManager em;
public SomeObject(...parameters){
repository.save();
em.flush();
repository.delete();
}