JPA认为我正在删除一个脱离管理状态的对象

43

我有一个DAO,使用JPA加载和保存我的领域对象。我最终设法让事务处理正常工作,现在我遇到了另一个问题。

在我的测试用例中,我调用我的DAO来加载具有给定ID的领域对象,检查它是否已加载,然后调用相同的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);

有没有人能告诉我我在这里做错了什么?

7个回答

68

我怀疑您正在非事务环境下运行代码,导致您的finddelete操作在单独的持久化上下文中进行,find实际上返回了一个游离实例(因此JPA是正确的,您确实正在删除一个游离对象)。

将您的查找/删除序列放到事务内部。

更新:下面是第7.3.1章节事务持久性上下文的摘录:

如果您在没有活动事务的情况下使用带有事务持久性上下文模型的EntityManager,每个方法调用都会创建一个新的持久性上下文,执行方法操作,并终止持久性上下文。例如,在事务之外使用EntityManager.find方法。 EntityManager将创建临时持久性上下文,执行查找操作,结束持久性上下文,并将分离的结果对象返回给您。使用相同ID的第二个调用将返回第二个分离的对象。


8
这对我来说似乎很反直觉。我真的需要在一个不影响数据库(即find())的操作中包装一个事务,以便我可以删除(或保存或更新)它吗? - Steve
虽然这似乎违反直觉,但它确实有效。这似乎意味着我需要完全重新思考我的DAO设计。听起来好像每个最终会修改实体的操作都必须先进行查找(在将用于编写实体的相同事务下)。 - Steve
@Steve 你可能觉得这很不直观,但实际上就是这样的。如果你在一个事务之外使用 find ,你会得到一个"脱离的"实体。 - Pascal Thivent
我建议尝试使用@ChiragDasani的答案,而不是在一个事务中执行所有查找和删除操作。 - Ghashange

35
public void remove(Object obj){
    em.remove(em.merge(obj));
}

以上代码类似于zawhtut提出的建议


1
非常好的通用删除解决方案,因为您不需要知道键。 - Andrzej Jozwik
你救了我 :) - vinod

15

赞同Pascal Thivent的帖子并做一个跟进。

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

6

使用em.getReference()获取实例,而不是使用em.find()

例如,尝试:

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

那么它避免了查询数据库吗? - Pawan

4

这是我使用的内容(基于之前的答案)

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.findentityManager.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);
    }

0
我的解决方法是调用flush,以使更改生效,如下例所示:
@PersistanceContext
EntityManager em;

public SomeObject(...parameters){
    repository.save();
    em.flush();
    repository.delete();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接