22

1 対多の関係 (国->) の非常に単純な例を取り上げます。

国(裏面):

@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);
}

orphanRemovalに設定されていることに注意してくださいtrueStateTableエンティティは、エンティティの関連付け ( ) を別のもの ( ) に変更することに関心のあるクライアント アプリケーションによって提供されます(CountryしたがってcountryId = 67StateTableJPAcountryId = 68の逆側で、子エンティティをその親 (コレクション) から別の親 (コレクション) に移行します)。orphanRemoval=true逆に反対します)。

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

orphanRemovalが に設定されているにもかかわらず、trueHibernate が通常のUPDATEDML ステートメントを発行orphanRemovalし、リレーションシップ リンクが移行される (単に削除されるのではなく) ため、 の効果が完全に中断されることを期待しています。

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

仕様に従って動作しているのはどれですか? この場合、逆側からUPDATE削除する以外に、Hibernate にステートメントを発行させることは可能ですか?orphanRemoval


これは、双方向の関係を両側でより一貫性のあるものにするための試みにすぎません。

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


アップデート :

UPDATE指定されたコードが次のように変更されている場合、Hibernate は予期される 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()に置き換えられているEntityManager#find()場合、DELETEDML ステートメントが予期せず発行されます。

それで、ここで何が起こっているのですか?特に携帯性を重視しています。この種の基本機能をさまざまな JPA プロバイダーに移植しないと、ORM フレームワークの使用が著しく損なわれます。

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

4

2 に答える 2

4

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

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

孤立した削除を保持するかどうかを決定します。保持したい場合は、新しい親を移動するのではなく、新しい子を作成する必要があります。

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

于 2015-12-11T07:44:40.360 に答える