web-dev-qa-db-fra.com

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 à l'aide de JPA. J'ai finalement réussi à faire fonctionner les transactions, maintenant j'ai un autre problème.

Dans mon cas de test, j'appelle mon DAO pour charger un objet de domaine avec un identifiant donné, vérifie qu'il a bien été chargé, puis appelle le même DAO pour supprimer l'objet que je viens de charger. Quand je fais ça, j'obtiens ce qui suit:

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 (sauf si Spring le fait sans que je le sache), comment peut-il s'agir d'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 cas 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 mal ici?

40
Steve

Je soupçonne que vous exécutez votre code en dehors d’une transaction afin que vos opérations find et delete se produisent dans un contexte de persistance séparé et que la find renvoie en fait une instance détachée (donc JPA a raison et vous SUPPRIMEZ 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 de transaction :

Si vous utilisez une variable EntityManager avec un modèle de contexte de persistance de transaction en dehors d'une transaction active, chaque appel de méthode crée un nouveau contexte de persistance, effectue 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. La EntityManager créera un contexte de persistance temporaire, effectuera l'opération de recherche, mettra fin au contexte de persistance et vous retournera l'objet de résultat détaché. Un deuxième appel avec le même identifiant renverra un deuxième objet détaché. 

66
Pascal Thivent
public void remove(Object obj){
    em.remove(em.merge(obj));
}

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

32
Sagar R. Kapadia

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

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

Obtenez l'instance en utilisant em.getReference() au lieu de em.find().

Par exemple, essayez:

em.remove(em.getReference(INTFC.class, id)); 
6
Chirag Dasani

Voici ce que j'ai utilisé (basé sur les 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();
}
3
Guillemo Mansilla

Transaction garantit les propriétés ACID, mais pas si l'entité est attachée ou détachée . Même si vous exécutez entityManager.find et entityManager.remove() dans la même transaction, rien ne garantit que l'entité sera attachée. Donc, avant d’émettre entityManager.remove(), vérifiez si l’entité est attachée. Si ce n’est pas le cas, associez-la à l’aide de enitityManger.merge(entity) puis émettez entityManager.remove comme suit: 

@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
App Work