web-dev-qa-db-ja.com

JPAカスケードが持続し、分離されたエンティティへの参照がPersistentObjectExceptionをスローします。どうして?

エンティティバーを参照するエンティティFooがあります。

@Entity
public class Foo {

    @OneToOne(cascade = {PERSIST, MERGE, REFRESH}, fetch = EAGER)
    public Bar getBar() {
        return bar;
    }
}

新しいFooを永続化すると、新しいバーまたは既存のバーへの参照を取得できます。それがたまたまデタッチされている既存のバーを取得すると、私の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を永続化できなくなります。マネージドバーへの参照を作成するには、永続化する前に次のようなコードが必要です。

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

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

既存のBar参照が与えられた場合、JPAが行う必要があるのは、そのIDを取得して、Fooを保持するテーブルの列に挿入することだけです。これは、Barが接続されている場合はこれを実行しますが、Barが接続されていない場合は例外をスローします。

私の質問です。なぜバーを付ける必要があるのですか?確かに、Barインスタンスがデタッチ状態からアタッチされた状態に移行しても、IDは変更されません。ここで必要なのはそのIDだけです。

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

33
Arjan Tijms

この場合、merge()の代わりにpersist()を使用できます。

_foo = entityManager.merge(foo); 
_

merge()を新しいインスタンスに適用すると、永続的になり(実際には、同じ状態の永続的なインスタンスが返されます)、手動で実行しようとすると、カスケードされた参照がマージされます。

21
axtavt

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

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

編集:

他の人が述べたように、cascade merge()を使用すると、新しいエンティティが永続化されるだけでなく、変更されたエンティティがマージされ、MERGEカスケードオプションと関係のある分離されたエンティティが再アタッチされます。 PERSISTカスケードオプションを使用しても、再接続やマージは行われず、それが目的の動作である場合に使用するためのものです。

7
Jim Tough