2010-11-28 5 views
32

JPAカスケード永続化および分離されたエンティティへの参照はPersistentObjectExceptionをスローします。どうして?

新しいFooを永続化すると、新しいBarまたは既存のBarのいずれかへの参照を取得できます。既存のBarが切り離されると、JPAプロバイダ(Hibernate)は次の例外をスローします。

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.Bar 
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:102) 
at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:636) 
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:628) 
at org.hibernate.engine.EJB3CascadingAction$1.cascade(EJB3CascadingAction.java:28) 
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:291) 
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:239) 
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192) 
at org.hibernate.engine.Cascade.cascade(Cascade.java:153) 
at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:454) 
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:288) 
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204) 
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130) 
at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49) 
at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154) 
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110) 
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61) 
at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:645) 
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619) 
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623) 
at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220) 
... 112 more 

Barへの参照が管理(添付)されているか、関係のPERSISTカスケード、すべて正常に動作します。

どちらの解決法も100%満足です。カスケードを削除しても、私は明らかにFooを新しいBarへの参照で永続させることはできません。 Barを管理するためのリファレンスを作成するには、これを永続化する前に次のようなコードが必要です。

if (foo.getBar().getID() != null && !entityManager.contains(foo.getBar())) { 
    foo.setBar(entityManager.merge(foo.getUBar())); 
} 
entityManager.persist(foo); 

単一のBarの場合、これは大きな問題ではないかもしれませんが、このようにすべてのプロパティを考慮する必要があれば、最初はORMを使用する理由を打ち負かしていると思われるかなり恐ろしいコードで始まります。私は、JDBCを使って手動でオブジェクトグラフを手動で保持することもできます。

JPAが持っている唯一のことは、既存のBarリファレンスが与えられたときにIDを取得し、それをFooを保持するテーブルの列に挿入することです。 Barが接続されているときはこれが正確ですが、Barが切り離されたときは例外がスローされます。

私の質問は、なぜバーが取り付けられる必要がありますか? Barインスタンスがデタッチ状態からアタッチ状態に移行するときに、そのIDは変更されず、そのIDがここで必要な唯一のものであるように思われます。

おそらくこれはHibernateのバグですか、何か不足していますか?

+2

カスケードからPERSISTを削除して、if(id == null){em.persiste(foo.getBar())}を確認してください。 – amorfis

+0

実際、それは実際には少しシンプルになります。提案していただきありがとうございます。 –

+0

もちろん、if文を少し単純にしても、永続化するコードはオブジェクトグラフ全体を辿る必要があります。これは避けようとしています。 –

答えて

21

あなたは、この場合にmerge()代わりのpersist()使用することができますあなたが手動でやろうとするように。

+0

確かに、そのトリックを行うようです。 merge()は、基本的には、伝統的な永続化と更新操作の両方として機能します。私はjavadocがこの問題について少しはっきりしていたと思う。私はまだpersist()が理由がないように見えるにもかかわらず、参照が管理されていると主張する理由を完全に理解していませんが、実際にはmerge()はかなり良い選択肢のようです。 –

+0

idによってではなく、他の条件によって "マージ"を行うことは可能ですか?私は外部ソースからオブジェクトを取得し、名前と通りの誕生日が一致する場合は、そのオブジェクトを取得したい。しかし、オブジェクトはカスケード永続ツリーの方が深いので、手動でデータベースからロードすることはしません。 – flaschenpost

+0

「マージ」メソッドと「永続化」メソッドを使用するのはどうですか?この場合、「マージ」メソッドを使用してオブジェクトを作成する必要があります。これはかなり奇妙です。または私は何かを欠いている? – Mat

7

私が正しく理解していれば、新しいFooが永続化するときに外部キー値(既存のBar)を持つことができるようにするには、Bar参照が必要です。この場合、getReference()と呼ばれるEntityManagerにJPAメソッドがあります。 getReference()メソッドは、find()に似ていますが、永続コンテキストにキャッシュされていない限り、管理対象インスタンス(Bar)を返すことはありません。 Fooオブジェクトを永続化するために、外部キーのニーズを満たすプロキシオブジェクトを返します。私はこれがあなたが望んでいた解決策の種類であるかどうかはわかりませんが、それがあなたに役立つかどうか試してみてください。

あなたのコードから、getterメソッドに注釈を付けることによって(「Bar」関係の場合)、「フィールド」スタイルのアクセスではなく「プロパティ」スタイルのアクセスが使用されていることにも気付きました。その理由は何ですか?パフォーマンス上の理由から、ゲッターではなくメンバーに注釈を付けることをお勧めします。 JPAプロバイダがgetterやsetter経由ではなく直接フィールドにアクセスする方が効率的であると考えられています。

EDIT:他の誰かが新しいエンティティと同様に、マージ変更のエンティティを持続し、MERGEカスケードオプションと関係を持って外しentitesを再接続しますカスケードマージ()を使用して、前述

通り。 PERSISTカスケードオプションを使用すると、何かを再結合したり、何かをマージしたりすることはありません。新しいインスタンスに適用された場合、merge()は、それが永続的になり

foo = entityManager.merge(foo); 

(実際には - 同じ状態で永続的なインスタンスを返します)、およびマージは、参照をカスケード接続:

+0

あなたは正しいです。 Barリファレンスは、既存のエンティティであるIFFは、新しいFooがFK to Barを持つことを許可するためにのみ必要です。 getReference()は、この場合find()やmerge()の代わりに安価な代替手段のように見えます。しかし、私は手動でオブジェクトグラフを歩き回り、すべての切り離された参照を添付された参照で置き換えることは望ましくないという問題が残っています。 JPAが管理参照を持つことを主張している理由、または集約ルートで再帰的に「すべてをアタッチ」できない理由を理解できていません。 –

+0

** JPAプロバイダがgetterやsetter経由ではなく直接フィールドにアクセスする方が効率的であると考えられています。**これまでに聞いたことはありませんでしたが、ヒントはありがたいです! –

+2

すべてのJPA実装では効率的ではありません。他のもの(例:DataNucleus)は両方の方法で同じ効率を提供します – DataNucleus

関連する問題