web-dev-qa-db-ja.com

JPAは、デタッチされたオブジェクトを削除していると考えています

JPAを使用してドメインオブジェクトをロードおよび保存するために使用したDAOがあります。私はついにトランザクションのものを機能させることができましたが、今は別の問題があります。

テストケースでは、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インスタンスを使用しており、EntityManagerを変更していない場合(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);

誰も私がここで間違っていることを教えてもらえますか?

41
Steve

トランザクションの外部でコードを実行しているため、findおよびdelete操作が個別の永続コンテキストで発生し、findは実際にdetachedインスタンス(JPAは正しいので、[〜#〜] are [〜#〜]デタッチされたオブジェクトの削除)。

トランザクション内で検索/削除シーケンスをラップします。

更新:章の抜粋の下 7.3.1。トランザクション持続コンテキスト

アクティブなトランザクションの外部でトランザクション永続コンテキストモデルでEntityManagerを使用する場合、各メソッド呼び出しは新しい永続コンテキストを作成し、メソッドアクションを実行し、永続コンテキストを終了します。たとえば、EntityManager.findトランザクション外のメソッド。 EntityManagerは、一時的な永続コンテキストを作成し、検索操作を実行し、永続コンテキストを終了し、分離された結果オブジェクトを返します。同じIDを持つ2番目の呼び出しは、2番目の分離オブジェクトを返します。

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

上記のコードはzawhtutによって提案されたものに似ています

33

Pascal Thiventの投稿とフォローアップに+1。

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

em.getReference()の代わりにem.find()を使用してインスタンスを取得します。

例えば、試してみてください:

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

ここに私が使用したものがあります(以前の回答に基づいて)

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

トランザクションは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);
    }
_
0
App Work