web-dev-qa-db-ja.com

Spring-Data JPA:既存のエンティティを参照する新しいエンティティを保存する

質問は基本的に以下のものと同じです:

JPAカスケードが持続し、デタッチされたエンティティへの参照がPersistentObjectExceptionをスローします。なぜですか?

既存の独立したエンティティを参照する新しいエンティティを作成しています。このエンティティをスプリングデータリポジトリに保存すると、例外がスローされます。

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist

springデータJPAのソースコードでsave()メソッドを見ると、次のように表示されます。

public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

そして、AbstractEntityInformationのisNew()を見ると

public boolean isNew(T entity) {

    return getId(entity) == null;
}

基本的に、新しいエンティティをsave()する場合は(id == null)、スプリングデータは常にpersistを呼び出すため、このシナリオは常に失敗します。

これは、新しいアイテムをコレクションに追加する際の非常に一般的な使用例のようです。

どうすれば解決できますか?

編集1:

注意:

この問題は、 Spring JPAで既存のエンティティを参照する新しいエンティティを保存する方法 に直接関係していません。詳細に説明すると、httpを介して新しいエンティティを作成する要求を受け取ったと仮定します。次に、リクエストから情報を抽出し、エンティティと既存の参照エンティティを作成します。したがって、それらは常に切り離されます。

30
beginner_

私が思いついた最高は

_public final T save(T containable) {
    // if entity containable.getCompound already exists, it
    // must first be reattached to the entity manager or else
    // an exception will occur (issue in Spring Data JPA ->
    // save() method internal calls persists instead of merge)
    if (containable.getId() == null
            && containable.getCompound().getId() != null){
        Compound compound = getCompoundService()
                .getById(containable.getCompound().getId());
        containable.setCompound(compound);   
    }
    containable = getRepository().save(containable);
    return containable; 
}
_

問題のある状況にあるかどうかを確認し、はいの場合は、そのIDによってデータベースから既存のエンティティをリロードし、新しいエンティティのフィールドをこの新しくロードされたインスタンスに設定します。その後、添付されます。

これには、新しいエンティティのサービスが、参照先エンティティのサービスへの参照を保持している必要があります。サービスを新しい_@Autowired_フィールドとして追加できるように、とにかくスプリングを使用しているため、これは問題になりません。

ただし、別の問題(私の場合、この動作は実際に必要です)は、新しいエンティティを保存するときに、参照されている既存のエンティティを同時に変更できないということです。これらの変更はすべて無視されます。

重要な注意点:

多くの場合、おそらくあなたの場合、これははるかに簡単です。エンティティマネージャーの参照をサービスに追加できます。

_@PersistenceContext
private EntityManager entityManager;
_

上記のif(){}ブロックの使用

_containable = entityManager.merge(containable);
_

私のコードの代わりに(動作するかどうかはテストされていません)。

私の場合、クラスは抽象的であり、したがって_@ManyToOne_のtargetEntityも抽象的です。 entityManager.merge(containable)を直接呼び出すと、例外が発生します。ただし、クラスがすべて具象である場合、これは機能するはずです。

9
beginner_

既に保存されたエンティティオブジェクトを内部に含む新しいエンティティオブジェクトを保存しようとしていたときに、同様の問題が発生しました。

Persistable <T>を実装し、それに応じてisNew()を実装しました。

public class MyEntity implements Persistable<Long> {

    public boolean isNew() {
        return null == getId() &&
            subEntity.getId() == null;
    }

または、 AbstractPersistable を使用して、もちろんisNewをオーバーライドできます。

これがこの問題を処理する良い方法と見なされるかどうかはわかりませんが、それは私にとって非常にうまく機能し、その上非常に自然に感じました。

12
Jonas Geiregat

Idの一部として_@EmbeddedId_とビジネスデータで同じ問題があります。
エンティティが新しいかどうかを知る唯一の方法は、_(em.find(entity)==null)?em.persist(entity):em.merge(entity)_を実行することです

しかし、spring-dataはsave()メソッドのみを提供し、Persistable.isNew()メソッドをfind()メソッドで埋める方法はありません。

3
nakkun