私の特定のケースでは、識別子列戦略を利用しています。これは、私の JPA 実装 (Hibernate) が特別なDTYPE列を持つusersテーブルを作成することを意味します。この列には、エンティティのクラス名が含まれます。たとえば、ユーザーテーブルにはTrialUserとPayingUserのサブクラスを含めることができます。これらのクラス名はDTYPE列にあるため、EntityManager がデータベースからエンティティをロードするときに、どのタイプのクラスをインスタンス化するかがわかります。
エンティティ型を変換する 2 つの方法を試しましたが、どちらも汚いハックのように感じます。
- ネイティブ クエリを使用して列に対して手動で UPDATE を実行し、その値を変更します。これは、プロパティの制約が類似しているエンティティに対して機能します。
- ターゲット タイプの新しいエンティティを作成し、BeanUtils.copyProperties()呼び出しを実行してプロパティを移動し、新しいエンティティを保存してから、新しい Id を古い Id に手動で置き換える名前付きクエリを呼び出して、すべての外部キー制約が維持されます。
#1の問題は、この列を手動で変更すると、JPAがこのエンティティを更新/永続コンテキストに再アタッチする方法がわからないことです。ID 1234のPayingUserではなく、ID 1234 のTrialUserが必要です。失敗します。ここで、おそらく EntityManager.clear() を実行して、すべてのエンティティをデタッチ/Per をクリアすることができます。コンテキストですが、これはサービス Bean であるため、システムのすべてのユーザーの保留中の変更が消去されます。
#2 の問題は、TrialUserを削除すると、Cascade=ALL に設定したすべてのプロパティも削除されることです。すべての拡張オブジェクト グラフを削除するのではなく、別のユーザーにスワップしようとしているだけなので、これは悪いことです。
更新 1 : #2 の問題により、ほとんど使用できなくなったため、動作させることをあきらめました。より洗練されたハックは間違いなくナンバー 1 であり、私はこの点である程度の進歩を遂げました。重要なのは、最初に基礎となる Hibernate Session への参照を取得し (JPA 実装として Hibernate を使用している場合)、Session.evict(user) メソッドを呼び出して、永続化コンテキストからその単一のオブジェクトのみを削除することです。残念ながら、これに対する純粋な JPA サポートはありません。サンプルコードは次のとおりです。
// Make sure we save any pending changes
user = saveUser(user);
// Remove the User instance from the persistence context
final Session session = (Session) entityManager.getDelegate();
session.evict(user);
// Update the DTYPE
final String sqlString = "update user set user.DTYPE = '" + targetClass.getSimpleName() + "' where user.id = :id";
final Query query = entityManager.createNativeQuery(sqlString);
query.setParameter("id", user.getId());
query.executeUpdate();
entityManager.flush(); // *** PROBLEM HERE ***
// Load the User with its new type
return getUserById(userId);
この例外をスローする手動のflush()に注意してください。
org.hibernate.PersistentObjectException: detached entity passed to persist: com.myapp.domain.Membership
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:102)
at org.hibernate.impl.SessionImpl.firePersistOnFlush(SessionImpl.java:671)
at org.hibernate.impl.SessionImpl.persistOnFlush(SessionImpl.java:663)
at org.hibernate.engine.CascadingAction$9.cascade(CascadingAction.java:346)
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.cascadeCollectionElements(Cascade.java:319)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:265)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:242)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
at org.hibernate.engine.Cascade.cascade(Cascade.java:153)
at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:154)
at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:145)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:88)
at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58)
at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:996)
at org.hibernate.impl.SessionImpl.executeNativeUpdate(SessionImpl.java:1185)
at org.hibernate.impl.SQLQueryImpl.executeUpdate(SQLQueryImpl.java:357)
at org.hibernate.ejb.QueryImpl.executeUpdate(QueryImpl.java:51)
at com.myapp.repository.user.JpaUserRepository.convertUserType(JpaUserRepository.java:107)
Userが OneToMany Set を持つMembershipエンティティが問題を引き起こしていることがわかります。舞台裏で何が起こっているのか、このナットをクラックするのに十分なほど知りません.
更新 2 : これまでのところ、上記のコードに示すように DTYPE を変更してから、 entityManager.clear()を呼び出すだけで機能します。
永続化コンテキスト全体をクリアすることの影響を完全には理解していません。代わりに、更新される特定のエンティティでSession.evict()を動作させたいと思っていました。