JPA считает, что я удаляю отдельный объект - PullRequest
40 голосов
/ 12 марта 2010

У меня есть 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, и я не менял EntityManager (если только 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);

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

Ответы [ 6 ]

66 голосов
/ 12 марта 2010

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

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

Обновление: Ниже приведен отрывок главы 7.3.1. Контекст постоянства транзакции :

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

33 голосов
/ 01 марта 2012
public void remove(Object obj){
    em.remove(em.merge(obj));
}

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

15 голосов
/ 25 ноября 2010

+ 1 к сообщению Паскаля Тивента и просто продолжение.

    @Transactional
    public void remove(long purchaseId){
        Purchase attached = jpaTemplate.find(Purchase.class,purchaseId);
        jpaTemplate.remove(attached);
    }
6 голосов
/ 07 ноября 2012

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

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

em.remove(em.getReference(INTFC.class, id)); 
4 голосов
/ 25 ноября 2013

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

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 голосов
/ 14 февраля 2017

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