web-dev-qa-db-ja.com

JPA:DELETE WHEREは子を削除せず、例外をスローします

JPQLクエリのおかげで、MOTHERから多数の行を削除しようとしています。

Motherクラスは次のように定義されます。

@Entity
@Table(name = "MOTHER")
public class Mother implements Serializable {

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "mother", 
               orphanRemoval = true)
    private List<Child> children;    
}

@Entity
@Table(name = "CHILD")
public class Child  implements Serializable {

    @ManyToOne
    @JoinColumn(name = "MOTHER_ID")
    private Mother mother;    
}

ご覧のとおり、Motherクラスには "children"があり、次のクエリを実行すると:

String deleteQuery = "DELETE FROM MOTHER WHERE some_condition";
entityManager.createQuery(deleteQuery).executeUpdate();

例外がスローされます:

ERROR - ORA-02292: integrity constraint <constraint name> violated - 
                   child record found

もちろん、削除するすべてのオブジェクトを選択してリストに取得してから、取得したすべてのオブジェクトを削除するまで繰り返し処理することもできますが、このようなソリューションのパフォーマンスはひどいものになります!

したがって、以前のマッピングを利用して、のクエリを最初に記述することなく、すべてのMotherオブジェクトとそれらに関連付けられたすべてのChildオブジェクトを効率的に削除できます。 all 子供たち?

24
Jean Logeart

DELETE(およびINSERT)は、JPQLクエリの関係を介してカスケードされません。これは仕様で明確に綴られています:

削除操作は、指定されたクラスとそのサブクラスのエンティティにのみ適用されます。関連するエンティティにはカスケードしません。

幸運なことに、エンティティマネージャーを使用して永続化および削除を行います(カスケード属性が定義されている場合)。

できること:

  • 削除する必要があるすべてのMotherエンティティインスタンスを取得します。
  • それぞれについて、EntityManager.remove()を呼び出します。

コードは次のようなものです。

String selectQuery = "SELECT m FROM Mother m WHERE some_condition";  
List<Mother> mothersToRemove = entityManager.createQuery(selectQuery).getResultList();  
for (Mother m: mothersToRemove) {  
    em.remove(m);
}
32
Mikko Maunu

session.delete() 、または同等の EntityManager.remove() を使用してみましたか?

HQLのdeleteステートメントを使用してクエリを発行する場合、Hibernateのカスケードメカニズムをバイパスしている可能性があります。このJIRAの問題をご覧ください: HHH-368

次の方法でこれを達成できる可能性があります。

Mother mother = session.load(Mother.class, id);
// If it is a lazy association, 
//it might be necessary to load it in order to cascade properly
mother.getChildren(); 
session.delete(mother);

コレクションを適切にカスケードするためにコレクションを初期化する必要があるかどうか、今はわかりません。

2
Xavi López

外部キー制約を使用して、これらのMothersを削除するためにRDBMSでリレーできます。これは、エンティティからDDLを生成することを前提としています。

@Entity
@Table(name = "CHILD")
public class Child  implements Serializable {

    @ManyToOne
    @JoinColumn(name = "MOTHER_ID", foreignKey = @ForeignKey(foreignKeyDefinition =
        "FOREIGN KEY(MOTHER_ID) REFERENCES MOTHER(ID) ON DELETE CASCADE",
        value = ConstraintMode.CONSTRAINT))
    private Mother mother;    
}
1
rzymek

これは関連しており、Hibernateを使用している場合に解決策を提供する場合があります。

JPA CascadeType.ALLは孤児を削除しません

編集

Oracleがエラーを提供しているので、Oracleカスケード削除を使用してこれを回避できます。ただし、これは予測できない結果になる可能性があります。他のレコードを削除していることをJPAが認識しないため、それらのオブジェクトは削除されてもキャッシュに残り、使用できます。これは、使用しているJPAの実装にキャッシュがあり、それを使用するように構成されている場合にのみ適用されます。

Oracleでのカスケード削除の使用に関する情報は次のとおりです。 http://www.techonthenet.com/Oracle/foreign_keys/foreign_delete.php

0
Sarel Botha

「MikKo Maunu」が言っているように、クエリの「delete」があなたのケースのすべての関連するonetomanyエンティティを削除しないかどうかはわかりません。そうだと思います。問題は、(これを試していないためごめんなさい)JPA/Hibernateが行うことは、「実際のsql削除」を実行することであり、それらの母と子のインスタンスはその時点では管理されないが、どの子を知る方法がない削除するインスタンスも。 orphanRemovalは非常に役立ちますが、この場合は役立ちません。私は...するだろう

1) 'fetch = FetchType.EAGER'をonetomany関係に追加してみてください(これもパフォーマンスの問題である可能性があります)

2)1)が機能しない場合、すべての母/子フェッチを実行してJPAレイヤーのすべてを明確にし、使用するものの前にクエリを実行するだけではありません(同じトランザクションで、しかし必要がないかわかりませんそれらの間で「em.flush」を実行します)

DELETE FROM Child c WHERE c.mother <the condition>

(多くの場合、削除はJPA/Hibernateにとって厄介なものであり、ORMの使用を非難するために使用する1つの例です。通常、開発段階で発見されます。私のお金は常にMyBatisにあります。

更新:

Mikko Maunuは正しい、JPQLの一括削除はカスケードされません。私が提案したように2つのクエリを使用しても結構です。

トリッキーなことは、永続コンテキスト(EntityManagerによって管理されるすべてのエンティティ)は一括削除の動作と同期しないため、個別のトランザクションで実行する必要があることです(私が提案する場合は両方のクエリ)。

PDATE 2:一括削除の代わりに手動削除を使用する場合、多くのJPAプロバイダーとHibernateでもEntityManager実装でremoveAll(...)メソッドまたは同様の(非API)を提供します。使用する方が簡単で、パフォーマンスに関してはより効果的かもしれません。

例えばOpenJPAは、EMをOpenJPAEntityManagerにキャストするだけでよく、OpenJPAPersistence.cast(em).removeAll(...)が最適です。

0
MarianP