43 votes

JPA pense que je supprime un objet détaché

J'ai un DAO que j'ai utilisé pour charger et sauvegarder mes objets de domaine en utilisant JPA. J'ai finalement réussi à faire fonctionner le système de transaction, mais j'ai maintenant un autre problème.

Dans mon scénario de test, j'appelle mon DAO pour charger un objet de domaine avec un id donné, je vérifie qu'il a été chargé et j'appelle ensuite le même DAO pour supprimer l'objet que je viens de charger. Lorsque je fais cela, j'obtiens le résultat suivant :

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)

Maintenant, étant donné que j'utilise la même instance DAO, et que je n'ai pas changé d'EntityManagers (à moins que Spring ne le fasse sans me le faire savoir), comment cela peut-il être un objet détaché ?

Mon code DAO ressemble à ceci :

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

Le code du scénario de test ressemble à ceci :

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

Quelqu'un peut-il me dire ce que je fais de mal ici ?

68voto

Pascal Thivent Points 295221

Je soupçonne que vous exécutez votre code en dehors d'une transaction, donc votre find et delete se produisent dans un contexte de persistance distinct et les find renvoie en fait un détaché instance (donc JPA a raison et vous SONT la suppression d'un objet détaché).

Enveloppez votre séquence de recherche/suppression dans une transaction.

Mise à jour : Ci-dessous un extrait du chapitre 7.3.1. Contexte de persistance des transactions :

Si vous utilisez un EntityManager avec un modèle de contexte de persistance transactionnel en dehors d'une transaction active, chaque invocation de méthode crée un nouveau contexte de persistance, exécute l'action de la méthode et met fin au contexte de persistance. Par exemple, envisagez d'utiliser la méthode EntityManager.find en dehors d'une transaction. Le site EntityManager crée un contexte de persistance temporaire, effectue l'opération de recherche, met fin au contexte de persistance et vous renvoie l'objet de résultat détaché. Un deuxième appel avec le même identifiant renverra un deuxième objet détaché.

35voto

Sagar R. Kapadia Points 201
public void remove(Object obj){
    em.remove(em.merge(obj));
}

Le code ci-dessus est similaire à celui proposé par zawhtut

15voto

zawhtut Points 3423

+1 au post de Pascal Thivent et juste un suivi.

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

6voto

Chirag Dasani Points 75

Obtenez une instance en utilisant em.getReference() au lieu de em.find() ;

comme...

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

4voto

Guillemo Mansilla Points 1575

Voici ce que j'ai utilisé (sur la base des réponses précédentes)

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X