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?
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éthodeEntityManager.find
en dehors d'une transaction. LaEntityManager
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é.
public void remove(Object obj){
em.remove(em.merge(obj));
}
Le code ci-dessus est similaire à celui proposé par zawhtut
+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);
}
Obtenez l'instance en utilisant em.getReference()
au lieu de em.find()
.
Par exemple, essayez:
em.remove(em.getReference(INTFC.class, id));
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();
}
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);
}