JPA 认为我正在删除一个分离的对象
Posted
技术标签:
【中文标题】JPA 认为我正在删除一个分离的对象【英文标题】:JPA thinks I'm deleting a detached object 【发布时间】:2011-01-26 14:06:48 【问题描述】:我有一个 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);
谁能告诉我我在这里做错了什么?
【问题讨论】:
【参考方案1】:我怀疑您是在事务之外运行代码,因此您的 find
和 delete
操作发生在单独的持久性上下文中,find
实际上返回一个 分离 实例(所以 JPA是对的,您正在删除一个分离的对象)。
将您的查找/删除序列包装在事务中。
更新:下面是章节7.3.1. Transaction Persistence Context的摘录:
如果您将
EntityManager
与活动事务之外的事务持久性上下文模型一起使用,则每个方法调用都会创建一个新的持久性上下文,执行方法操作并结束持久性上下文。例如,考虑在事务之外使用EntityManager.find
方法。EntityManager
将创建一个临时持久化上下文,执行查找操作,结束持久化上下文,并将分离的结果对象返回给您。具有相同 id 的第二次调用将返回第二个分离的对象。
【讨论】:
这对我来说确实违反直觉。我真的需要在事务中包装一个不影响数据库的操作(find()),以便我可以删除(或保存或更新)它吗? 虽然可能违反直觉,但它确实有效。这似乎意味着我需要完全重新调整我的 DAO 设计。听起来像 每个 操作最终会修改一个实体必须首先进行查找(在将用于写入实体的同一事务下)。 @Steve 你可能会觉得它违反直觉,但这就是事情的运作方式。如果你在事务之外使用find
,你会得到一个分离的实体。
我建议尝试使用@ChiragDasani 的答案,而不是在一个事务中完成所有查找和删除内容。【参考方案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();
【讨论】:
以上是关于JPA 认为我正在删除一个分离的对象的主要内容,如果未能解决你的问题,请参考以下文章
在 Spring Boot 中使用 JPA 从具有 OneToMany 关系的实体中删除对象
有没有办法将分离的对象传递给 JPA 持久化? (分离的实体传递给坚持)