2015-11-30 9 views
20

1対多の関係(国->状態)の非常に簡単な例をとります。orphanRemovalがtrueに設定されたエンティティアソシエーションを持つエンティティをマージしている間、Hibernateが孤立したエンティティを削除しないようにする

国(逆側):

@OneToMany(mappedBy = "country", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) 
private List<StateTable> stateTableList=new ArrayList<StateTable>(0); 

StateTable(所有側)

@JoinColumn(name = "country_id", referencedColumnName = "country_id") 
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH}) 
private Country country; 

供給(デタッチ)StateTableアクティブ・データベーストランザクション内のエンティティ(JTAを更新しようとする方法又はリソースローカル):

public StateTable update(StateTable stateTable) { 

    // Getting the original state entity from the database. 
    StateTable oldState = entityManager.find(StateTable.class, stateTable.getStateId()); 
    // Get hold of the original country (with countryId = 67, for example). 
    Country oldCountry = oldState.getCountry(); 
    // Getting a new country entity (with countryId = 68) supplied by the client application which is responsible for modifying the StateTable entity. 
    // Country has been changed from 67 to 68 in the StateTable entity using for example, a drop-down list. 
    Country newCountry = entityManager.find(Country.class, stateTable.getCountry().getCountryId()); 
    // Attaching a managed instance to StateTable. 
    stateTable.setCountry(newCountry); 

    // Check whether the supplied country and the original country entities are equal. 
    // (Both not null and not equal - http://stackoverflow.com/a/31761967/1391249) 
    if (ObjectUtils.notEquals(newCountry, oldCountry)) { 
     // Remove the state entity from the inverse collection held by the original country entity. 
     oldCountry.remove(oldState); 
     // Add the state entity to the inverse collection held by the newly supplied country entity 
     newCountry.add(stateTable); 
    } 

    return entityManager.merge(stateTable); 
} 

なお、orphanRemovaltrueに設定されています。 StateTableエンティティは、エンティティの関連付けCountrycountryId = 67)をStateTableに変更することに関心のあるクライアントアプリケーションから提供されます(したがって、JPAの逆の側で、子エンティティを親(コレクション)から別のもの親(コレクション)orphanRemoval=trueが順番に反対します)。

Hibernateプロバイダは、DML文を発行して、StateTableエンティティに対応する行を基になるデータベーステーブルから削除します。

orphanRemovaltrueに設定されているという事実にもかかわらず、私は(単に削除されていない)関係のリンクが移行されるため、orphanRemovalの効果が完全に懸濁させる通常のUPDATE DML文を発行するために休止状態を期待しています。

EclipseLinkはまさにその仕事をします。指定されたシナリオ(と同じ関係を持つ、trueに設定)でUPDATEステートメントを発行します。

仕様によってどちらが動作していますか?この場合、Hibernateが逆の側からorphanRemovalを取り除く以外にUPDATE文を発行することは可能ですか?


これは、両側の双方向の関係がより一貫性にする唯一の試みです。

防御リンク管理方法、すなわちadd()と上記のスニペットに使用remove()次のように、必要な場合は、Countryエンティティで定義されています。

public void add(StateTable stateTable) { 
    List<StateTable> newStateTableList = getStateTableList(); 

    if (!newStateTableList.contains(stateTable)) { 
     newStateTableList.add(stateTable); 
    } 

    if (stateTable.getCountry() != this) { 
     stateTable.setCountry(this); 
    } 
} 

public void remove(StateTable stateTable) { 
    List<StateTable> newStateTableList = getStateTableList(); 

    if (newStateTableList.contains(stateTable)) { 
     newStateTableList.remove(stateTable); 
    } 
} 


更新:与えられたコードは次のように変更された場合

Hibernateは唯一、予想UPDATE DML文を発行することができます。

public StateTable update(StateTable stateTable) { 
    StateTable oldState = entityManager.find(StateTable.class, stateTable.getStateId()); 
    Country oldCountry = oldState.getCountry(); 
    // DELETE is issued, if getReference() is replaced by find(). 
    Country newCountry = entityManager.getReference(Country.class, stateTable.getCountry().getCountryId()); 

    // The following line is never expected as Country is already retrieved 
    // and assigned to oldCountry above. 
    // Thus, oldState.getCountry() is no longer an uninitialized proxy. 
    oldState.getCountry().hashCode(); // DELETE is issued, if removed. 
    stateTable.setCountry(newCountry); 

    if (ObjectUtils.notEquals(newCountry, oldCountry)) { 
     oldCountry.remove(oldState); 
     newCountry.add(stateTable); 
    } 

    return entityManager.merge(stateTable); 
} 

新しいバージョンのコードでは、次の2行を確認してください。

// Previously it was EntityManager#find() 
Country newCountry = entityManager.getReference(Country.class, stateTable.getCountry().getCountryId()); 
// Previously it was absent. 
oldState.getCountry().hashCode(); 

最後の行が存在しないか、またはEntityManager#getReference()DELETE DML文が予期せずに発行され、その後、EntityManager#find()に置き換えられます。いずれかの場合

ここでは何が起こっていますか?特に、私は移植性を重視しています。この種類の基本を異なるJPAプロバイダに移植しないと、ORMフレームワークの使用が厳しく敗北します。

私はEntityManager#getReference()EntityManager#find()の基本的な違いを理解しています。

+0

削除する前に追加すると、ちょっと考えても違いはありますか? –

+0

これらの2行の順序を変更しても違いはありません。私はあらかじめそのような可能性をチェックしています:) – Tiny

+0

Hibernateのバグのようです。ちょうど推測では、新しい国を明示的にマージし、その後、国家の前の古い国をマージします。必要に応じて、間にflushを呼び出します。 – BalusC

答えて

9

まず、のは、シンプルなフォームにあなたの元のコードを変更してみましょう:私は唯一の3行目にoldState.getCountry().hashCode()を追加

StateTable oldState = entityManager.find(StateTable.class, stateTable.getStateId()); 
Country oldCountry = oldState.getCountry(); 
oldState.getCountry().hashCode(); // DELETE is issued, if removed. 

Country newCountry = entityManager.find(Country.class, stateTable.getCountry().getCountryId()); 
stateTable.setCountry(newCountry); 

if (ObjectUtils.notEquals(newCountry, oldCountry)) { 
    oldCountry.remove(oldState); 
    newCountry.add(stateTable); 
} 

entityManager.merge(stateTable); 

注意してください。これで、この行だけを削除して問題を再現することができます。

ここで何が起こっているのかを説明する前に、最初にJPA 2.1 specificationから抜粋してください。

3.2.4

フラッシュ操作の意味は、Xが の通りですエンティティに適用される次の:Xが管理するエンティティである場合

  • 、それが同期されていますデータベースに転送します。 YがXから関係によって参照されるすべてのエンティティについて
    • Yとの関係が カスケード= = ALL持続またはカスケードカスケードの要素の値で注釈されている場合、永続動作はY
  • 印加されます

セクション3.2。

  • Xが除去エンティティである場合は、管理対象となる:2: は、以下のように持続操作の

    セマンティクス、エンティティXに印加されています。

orphanRemoval JPA javadoc

を関係から削除され、それらのエンティティに削除 操作をカスケード接続することを企業に削除操作を適用するかどうか(オプション)。

私たちが見ることができるように、orphanRemovalremove操作の観点で定義されているので、removeのために適用されるすべてのルールが同様orphanRemovalを申請しなければなりません。

第2に、this answerで説明したように、Hibernateによって実行される更新の順序は、エンティティが永続コンテキストにロードされる順序です。より正確には、エンティティを更新するということは、現在の状態(ダーティチェック)をデータベースと同期させ、その操作をその関連付けにカスケードすることを意味します。

これはあなたのケースで起こっていることです。トランザクションが終了すると、Hibernateは永続コンテキストとデータベースを同期させます。余分な行(hashCode)が存在する場合

  1. :我々は2つのシナリオを持っている

    1. HibernateはDBとoldCountryを同期します。 oldCountryが最初にロードされたため(hashCodeを呼び出してプロキシの初期化を強制したため)、newCountryを処理する前に行います。
    2. HibernateはStateTableインスタンスがoldCountryのコレクションから削除されたことを確認して、StateTableインスタンスを削除済みとしてマークします。
    3. HibernateはnewCountryとDBを同期させます。 PERSIST操作はstateTableListにカスケード接続され、削除されたStateTableエンティティインスタンスが含まれています。
    4. 削除されたStateTableインスタンスが再び管理されるようになりました(前述のJPA仕様の3.2.2セクション)。
  2. 余分ライン(hashCode)が存在しない:

    1. HibernateはDBとnewCountry同期します。 newCountryが最初にロードされたため(entityManager.find)、oldCountryを処理する前に実行します。
    2. HibernateはoldCountryとDBを同期させます。
    3. HibernateはStateTableインスタンスがoldCountryのコレクションから削除されたことを確認して、StateTableインスタンスを削除済みとしてマークします。
    4. StateTableインスタンスの削除は、データベースと同期されます。

アップデートの順序はまた、あなたが基本的にDBからnewCountryをロードする前に発生するoldCountryプロキシの初期化を強制するあなたの調査結果を説明しています。

これはJPA仕様によるものですか?明らかに、JPA仕様のルールは破られていません。

なぜこれは移植性がありませんか?

JPA仕様(結局、他の仕様と同様)は、仕様に含まれていない多くの詳細をプロバイダが自由に定義できるようにします。

また、「ポータビリティ」の見解にもよります。 orphanRemovalの機能とその他のJPA機能は、正式な定義については移植性があります。ただし、JPAプロバイダの仕様と組み合わせて使用​​する方法によって異なります。ところで

、仕様のセクション 2.9orphanRemovalのために推奨しています(ただし、明確に定義していません):

ポータブルアプリケーションでは、そうでない場合は、特定の順序に依存してはならない除去の 、とはなりません孤立したエンティティを に再割り当てするか、そうでなければ永続化を試みます。

しかし除去エンティティの持続性は、明細書中の他の文で許可されているため、これは、仕様に曖昧か、明確に定義された推奨事項のほんの一例です。

+0

これでもう少し進んでいきます: 'Country oldCountry = entityManager.find(Country.class、oldState.getCountry()。getCountryId());'この行を追加する必要性を排除します 'oldState.getCountry()。hashCode(); 'しかし、私にとっては、' EntityManager#find() 'も必要ないはずです。この 'Country oldCountry = oldState.getCountry();'は、メソッドがアクティブなJTAトランザクション内で実行されるときに完全修飾エンティティを取得する必要があります。 'oldState.getCountry()'は、 'oldState.getCountry(); hashCode();'を実行して明示的にロードされない限り読み込めない 'EntityManager#getReference()'によって返される初期化されていないプロキシではありません – Tiny

+0

おそらく、 ''しかし、私はそれを使用することには興味がありません。 – Tiny

+0

@Tiny 'oldState.getCountry()'は初期化されていないプロキシを返します(アソシエーションは遅延です)。 'hibernate.enable_lazy_load_no_trans'はあなたの問題とは何の関係もありません。 –

3

参照先のエンティティを他の親で使用できるようになると、とにかく複雑になります。実際にそれをきれいにするために、ORMは、削除する前に削除されたエンティティの他の用途をデータベース内で検索しなければなりませんでした(永続ガベージコレクション)。これは時間がかかり、実際には有用ではないため、Hibernateで実装されていません。

孤児の削除は、あなたの子供が単一の親として使用され、他の場所では再利用されない場合にのみ機能します。この機能の誤用をよりよく検出するために再利用しようとすると、例外が発生することさえあります。

孤児の削除を維持するかどうかを決定します。それを保持したい場合は、新しい親を移動する代わりに新しい子を作成する必要があります。

孤児の削除を放棄した場合は、孤児を参照しなくなったらすぐに削除する必要があります。

+0

よろしく!これは、この種の基本機能の移植性の問題です。 JPA仕様書に明示されているはずです。 – Tiny

+0

私が指摘しようとしたように、すべてが記憶されていない可能性のあるシナリオについて考え始めるとすぐにはそれほど初歩的ではありません。とにかく、私はNHibernateからそれを知っているので、私は間違っている可能性があります。私はちょうどそれがそこに同じであると仮定しました。 –

関連する問題