it-swarm-id.com

JPA: DELETE WHERE tidak menghapus anak-anak dan melempar pengecualian

Saya mencoba menghapus sejumlah besar baris dari MOTHER berkat kueri JPQL.

Kelas Mother didefinisikan sebagai berikut:

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

Seperti yang Anda lihat, kelas Mother memiliki "anak-anak" dan ketika menjalankan kueri berikut:

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

pengecualian dilemparkan:

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

Tentu saja, saya pertama-tama dapat memilih semua objek yang ingin saya hapus dan mengambilnya ke dalam daftar sebelum mengulanginya untuk menghapus semua objek yang diambil, tetapi kinerja solusi seperti itu hanya akan mengerikan!

Jadi adakah cara untuk memanfaatkan pemetaan sebelumnya untuk menghapus semua objek Mother DAN semua objek Child yang dikaitkan dengannya secara efisien dan tanpa menulis terlebih dahulu pertanyaan untuk semua anak-anak?

24
Jean Logeart

HAPUS (dan INSERT) tidak mengalir melalui hubungan dalam permintaan JPQL. Ini jelas dieja dalam spesifikasi:

Operasi penghapusan hanya berlaku untuk entitas dari kelas yang ditentukan dan subkelasnya. Itu tidak mengalir ke entitas terkait.

Untungnya bertahan dan dihapus melalui manajer entitas lakukan (ketika ada atribut kaskade ditentukan). 

Apa yang dapat Anda lakukan:

  • ambil semua instance entitas Ibu yang harus dihapus.
  • untuk masing-masing dari mereka memanggil EntityManager.remove ().

Kode kira-kira seperti ini:

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

Sudahkah Anda mencoba menggunakan session.delete() , atau setara EntityManager.remove () ?

Saat Anda menggunakan pernyataan penghapusan HQL untuk mengeluarkan kueri, Anda mungkin melewati mekanisme cascading Hibernate. Lihatlah Masalah JIRA ini: HHH-368

Anda mungkin dapat mencapai ini dengan: 

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

Saya tidak yakin sekarang apakah perlu menginisialisasi koleksi untuk membuatnya mengalir dengan benar.

1
Xavi López

Ini terkait dan dapat menawarkan solusi jika Anda menggunakan Hibernate.

JPA CascadeType.ALL tidak menghapus anak yatim

EDIT

Karena Oracle adalah orang yang memberi Anda kesalahan, Anda mungkin dapat menggunakan penghapusan kaskade Oracle untuk menyiasati ini. Namun, ini bisa memiliki hasil yang tidak dapat diprediksi: karena JPA tidak menyadari bahwa Anda menghapus catatan lain objek-objek itu dapat tetap berada dalam cache dan digunakan meskipun telah dihapus. Ini hanya berlaku jika implementasi JPA yang Anda gunakan memiliki cache dan dikonfigurasi untuk menggunakannya.

Berikut adalah info tentang penggunaan penghapusan kaskade di Oracle: http://www.techonthenet.com/Oracle/foreign_keys/foreign_delete.php

0
Sarel Botha

Anda bisa menyampaikan pada RDBMS untuk menghapus Mothers tersebut menggunakan batasan kunci asing . Ini mengasumsikan Anda menghasilkan DDL dari entitas:

@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;    
}
0
rzymek

Saya harus mengatakan saya tidak yakin apakah 'hapus' dalam permintaan tidak akan menghapus semua entitas terkait yang ada dalam kasus Anda seperti kata 'MikKo Maunu'. Saya akan mengatakan itu akan . Masalahnya adalah (maaf karena tidak mencoba ini) bahwa apa yang akan dilakukan JPA/Hibernate adalah dengan hanya menjalankan 'real sql delete' dan sementara contoh Ibu dan Anak tersebut tidak dikelola pada saat itu , tidak memiliki cara untuk mengetahui instance Anak mana yang harus dihapus juga . orphanRemoval sangat membantu, tetapi tidak dalam kasus ini .

1) coba tambahkan 'fetch = FetchType.EAGER' ke dalam relasi onetomany (ini mungkin juga merupakan masalah kinerja)

2) jika 1) tidak berfungsi, tidak melakukan semua Ibu/Anak mengambil untuk membuat semuanya jelas untuk lapisan JPA, dan jalankan kueri sebelum yang Anda gunakan (dalam transaksi yang sama, tetapi saya tidak yakin jika Anda tidak perlu untuk menjalankan 'em.flush' di antara mereka) 

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

(Penghapusan sering menjadi gangguan dengan JPA/Hibernate dan salah satu contoh yang saya gunakan untuk mencela penggunaan ORM, yang pada dasarnya merupakan lapisan tambahan dalam aplikasi, untuk membuat segalanya lebih mudah. ​​Hanya hal baiknya adalah, bahwa ORM mengeluarkan/bug biasanya ditemukan selama tahap pengembangan. Uang saya selalu di MyBatis yang jauh lebih bersih menurut saya.)

PEMBARUAN:

Mikko Maunu benar, penghapusan massal dalam JPQL tidak mengalir. Menggunakan dua pertanyaan seperti yang saya sarankan baik-baik saja.

Yang rumit adalah, bahwa konteks kegigihan (semua entitas yang dikelola oleh EntityManager) tidak disinkronkan dengan apa yang dilakukan penghapusan massal, sehingga (kedua pertanyaan dalam kasus yang saya sarankan) harus dijalankan dalam transaksi terpisah.

PEMBARUAN 2: Jika menggunakan penghapusan manual dan bukan penghapusan massal, banyak penyedia JPA dan Hibernate juga menyediakan metode removeAll (...) atau yang serupa (non-API) pada implementasi EntityManager mereka. Ini lebih sederhana untuk digunakan dan mungkin lebih efektif dalam hal kinerja.

Misalnya OpenJPA Anda hanya perlu mengirimkan EM Anda ke OpenJPAEntityManager, terbaik oleh OpenJPAPersistence.cast (em) .removeAll (...)

0
MarianP