43

I've got a DAO that I used to load and save my domain objects using JPA. I finally managed to get the transaction stuff working, now I've got another issue.

In my test case, I call my DAO to load a domain object with a given id, check that it got loaded and then call the same DAO to delete the object I just loaded. When I do that I get the following:

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)

Now given that I'm using the same DAO instance, and I've not changed EntityManagers (unless Spring does so without letting me know), how can this be a detached object?

My DAO code looks like this:

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

The test case code looks like:

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

Can anyone tell me what I'm doing wrong here?

Sam
  • 7,252
  • 16
  • 46
  • 65
Steve
  • 2,678
  • 8
  • 40
  • 54

7 Answers7

68

I suspect that you are running your code outside a transaction so your find and delete operations occur in a separate persistence context and the find actually returns a detached instance (so JPA is right and you ARE deleting a detached object).

Wrap your find / delete sequence inside a transaction.

Update: Below an excerpt of the chapter 7.3.1. Transaction Persistence Context:

If you use an EntityManager with a transaction persistence context model outside of an active transaction, each method invocation creates a new persistence context, performs the method action, and ends the persistence context. For example, consider using the EntityManager.find method outside of a transaction. The EntityManager will create a temporary persistence context, perform the find operation, end the persistence context, and return the detached result object to you. A second call with the same id will return a second detached object.

Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
  • 8
    That really seems counter-intuitive to me. Do I really need to wrap an operation that doesn't impact the database (the find()) in a transaction, just so I can delete (or save or update) it? – Steve Mar 11 '10 at 21:37
  • Counter-intuitive though it may be, it does work. This seems to imply that I need to completely rethin my DAO design. It sounds like *every* operation that will eventually modify an Entity will have to do a find first (under the same transaction that will be used to write the Entity). – Steve Mar 11 '10 at 21:44
  • @Steve You may find it counter intuitive but this is how things work. If you use `find` outside a transaction, you'll get a detached entity. – Pascal Thivent Mar 11 '10 at 21:49
  • I suggest to try it with the answer of @ChiragDasani instead to do all the find and remove stuff in one transaction. – Ghashange Oct 16 '15 at 07:25
35
public void remove(Object obj){
    em.remove(em.merge(obj));
}

The above code is similar to that proposed by zawhtut

15

+1 to Pascal Thivent's post and just a followup.

    @Transactional
    public void remove(long purchaseId){
        Purchase attached = jpaTemplate.find(Purchase.class,purchaseId);
        jpaTemplate.remove(attached);
    }
zawhtut
  • 8,335
  • 5
  • 52
  • 76
6

Get the instance by using em.getReference() instead of em.find().

For instance, try:

em.remove(em.getReference(INTFC.class, id)); 
stites
  • 4,903
  • 5
  • 32
  • 43
Chirag Dasani
  • 1,205
  • 2
  • 10
  • 11
4

Here is what I used (based on the previous answers)

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();
}
Guillermo Mansilla
  • 3,779
  • 2
  • 29
  • 34
0

Transaction ensures the ACID properties but not whether the entity is attached or detached. Even if you are running entityManager.find and entityManager.remove() in the same transaction , there is not guarantee that the entity will be attached. So before issuing entityManager.remove() check if the entity is attached, if not attach it using enitityManger.merge(entity) and then issue entityManager.remove on it as follows:

@Transactional
 public void delete (long id)
    {
ModelObj modelObj=entityManager.find(ModelObj.class,id);
modelObj=entityManager.contains(modelObj)?modelObj:entityManager.merge(modelObj);
        em.remove (modelObj);
    }
App Work
  • 21,899
  • 5
  • 25
  • 38
0

What worked for me was calling flush so the changes are made as in the following example:

@PersistanceContext
EntityManager em;

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