2010-03-11 2 views
40

У меня есть DAO, который я использовал для загрузки и сохранения объектов домена с помощью JPA. Наконец-то мне удалось заставить работать транзакцию, теперь у меня другая проблема.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 (если весна не делает это, не давая мне знать), как это может быть отдельный объект?

Мой DAO код выглядит следующим образом: Код случай

Тест выглядит следующим образом:

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

Может кто-нибудь сказать мне, что я делаю неправильно здесь?

ответ

66

Я подозреваю, что вы используете свой код вне транзакции, чтобы ваши find и delete операций выполняется в отдельном контексте настойчивости и find фактически возвращает отдельностоящего экземпляра (так JPA является правильным, и вы ARE удаления отдельностоящим объект).

Оберните свою последовательность поиска/удаления внутри транзакции.

Update: Ниже отрывок из главы 7.3.1. Transaction Persistence Context:

Если вы используете EntityManager с транзакции инерционности контекстной модели за пределами активной транзакции, каждый вызов метода создает новый контекст сохранения, выполняет действие метода и завершает контекст персистентности. Например, рассмотрите возможность использования метода EntityManager.find вне транзакции. EntityManager создаст контекст временной постоянной, выполнит операцию поиска, завершит контекст персистентности и вернет вам выделенный объект результата. Второй вызов с тем же идентификатором вернет второй отдельный объект.

+7

Это действительно кажется нелогичным для меня. Мне действительно нужно обернуть операцию, которая не влияет на базу данных (find()) в транзакции, чтобы я мог удалить (или сохранить или обновить) ее? – Steve

+0

Контринтуитивный, хотя может быть, он действительно работает. Это, по-видимому, означает, что мне нужно полностью переосмыслить мой дизайн DAO. Это звучит как * каждая * операция, которая в конечном итоге изменит Entity, придется сначала выполнить поиск (под той же транзакцией, которая будет использоваться для записи Entity). – Steve

+0

@Steve Вы можете обнаружить, что это противоречит интуитивному, но это то, как все работает. Если вы используете 'find' вне транзакции, вы получите отдельный объект. –

14

+1 К сообщению Паскаля Тивента и только о последующих действиях.

@Transactional 
    public void remove(long purchaseId){ 
     Purchase attached = jpaTemplate.find(Purchase.class,purchaseId); 
     jpaTemplate.remove(attached); 
    } 
32
public void remove(Object obj){ 
    em.remove(em.merge(obj)); 
} 

Приведенный выше код аналогичен предложенному zawhtut

+1

Очень хорошее решение для общего удаления, потому что вам не нужно знать ключ. –

+0

вы меня спасли :) – vinod

6

Получить экземпляр, используя em.getReference() вместо em.find().

Например, попробуйте:

em.remove(em.getReference(INTFC.class, id)); 
+0

Так ли это избежать запросов к базе данных? – Pawan

3

Вот что я использовал (на основе предыдущих ответов)

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

транзакций обеспечивает свойства 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); 
    }