Menghapus Objek dengan Hibernate

1. Ikhtisar

Sebagai kerangka kerja ORM berfitur lengkap, Hibernate bertanggung jawab atas manajemen siklus hidup objek persisten (entitas), termasuk operasi CRUD seperti membaca , menyimpan , memperbarui , dan menghapus .

Pada artikel ini, kami menjelajahi berbagai cara di mana objek dapat dihapus dari database menggunakan Hibernate dan kami menjelaskan masalah umum dan jebakan yang mungkin terjadi.

Kami menggunakan JPA dan hanya mundur dan menggunakan API asli Hibernate untuk fitur-fitur yang tidak standar di JPA.

2. Berbagai Cara Menghapus Objek

Objek dapat dihapus dalam skenario berikut ini:

  • Dengan menggunakan EntityManager.remove
  • Saat penghapusan mengalir dari instance entitas lain
  • Saat orphanRemoval diterapkan
  • Dengan menjalankan pernyataan delete JPQL
  • Dengan menjalankan kueri asli
  • Dengan menerapkan teknik penghapusan lunak (memfilter entitas yang dihapus untuk sementara dengan kondisi di klausa @Where )

Di sisa artikel ini, kami melihat poin-poin ini secara rinci.

3. Penghapusan Menggunakan Manajer Entitas

Penghapusan dengan EntityManager adalah cara paling mudah untuk menghapus instance entitas:

Foo foo = new Foo("foo"); entityManager.persist(foo); flushAndClear(); foo = entityManager.find(Foo.class, foo.getId()); assertThat(foo, notNullValue()); entityManager.remove(foo); flushAndClear(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue()); 

Dalam contoh di artikel ini kami menggunakan metode helper untuk membersihkan dan menghapus konteks persistensi saat diperlukan:

void flushAndClear() { entityManager.flush(); entityManager.clear(); }

Setelah memanggil metode EntityManager.remove , transisi instance yang disediakan ke status yang dihapus dan penghapusan terkait dari database terjadi pada pembersihan berikutnya.

Perhatikan bahwa contoh yang dihapus tetap ada jika operasi PERSIST diterapkan padanya . Kesalahan umum adalah mengabaikan bahwa operasi PERSIST telah diterapkan ke contoh yang dihapus (biasanya, karena itu mengalir dari contoh lain pada waktu flush), karena bagian 3.2.2 spesifikasi JPA mengamanatkan bahwa contoh tersebut harus bertahan lagi dalam kasus seperti itu.

Kami mengilustrasikan ini dengan mendefinisikan asosiasi @ManyToOne dari Foo ke Bar :

@Entity public class Foo { @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) private Bar bar; // other mappings, getters and setters }

Saat kami menghapus instance Bar yang direferensikan oleh instance Foo yang juga dimuat dalam konteks persistensi, instance Bar tidak akan dihapus dari database:

Bar bar = new Bar("bar"); Foo foo = new Foo("foo"); foo.setBar(bar); entityManager.persist(foo); flushAndClear(); foo = entityManager.find(Foo.class, foo.getId()); bar = entityManager.find(Bar.class, bar.getId()); entityManager.remove(bar); flushAndClear(); bar = entityManager.find(Bar.class, bar.getId()); assertThat(bar, notNullValue()); foo = entityManager.find(Foo.class, foo.getId()); foo.setBar(null); entityManager.remove(bar); flushAndClear(); assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());

Jika Bar yang dihapus direferensikan oleh Foo , operasi PERSIST diturunkan dari Foo ke Bar karena pengaitannya ditandai dengan cascade = CascadeType.ALL dan penghapusan tidak terjadwal. Untuk memverifikasi bahwa ini terjadi, kami dapat mengaktifkan tingkat log jejak untuk paket org.hibernate dan mencari entri seperti penghapusan entitas penjadwalan .

4. Penghapusan Bertingkat

Penghapusan dapat diturunkan ke entitas turunan ketika orang tua dihapus:

Bar bar = new Bar("bar"); Foo foo = new Foo("foo"); foo.setBar(bar); entityManager.persist(foo); flushAndClear(); foo = entityManager.find(Foo.class, foo.getId()); entityManager.remove(foo); flushAndClear(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue()); assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());

Di sini bilah dihapus karena penghapusan dialirkan dari foo , karena pengaitan dinyatakan untuk mengalirkan semua operasi siklus hidup dari Foo ke Bar .

Perhatikan bahwa hampir selalu merupakan bug untuk melakukan operasi REMOVE berjenjang dalam asosiasi @ManyToMany , karena hal itu akan memicu penghapusan instance turunan yang mungkin terkait dengan instance induk lainnya. Ini juga berlaku untuk CascadeType.ALL , karena ini berarti bahwa semua operasi akan di-cascade, termasuk operasi REMOVE .

5. Pemindahan Anak Yatim Piatu

Perintah orphanRemoval mendeklarasikan bahwa instance entitas terkait harus dihapus ketika dipisahkan dari induknya, atau sama ketika entitas induk dihapus.

Kami menunjukkan ini dengan mendefinisikan asosiasi seperti itu dari Bar ke Baz:

@Entity public class Bar { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List bazList = new ArrayList(); // other mappings, getters and setters }

Kemudian instance Baz dihapus secara otomatis saat dihapus dari daftar instance Bar induk :

Bar bar = new Bar("bar"); Baz baz = new Baz("baz"); bar.getBazList().add(baz); entityManager.persist(bar); flushAndClear(); bar = entityManager.find(Bar.class, bar.getId()); baz = bar.getBazList().get(0); bar.getBazList().remove(baz); flushAndClear(); assertThat(entityManager.find(Baz.class, baz.getId()), nullValue());

Semantik operasi orphanRemoval sangat mirip dengan operasi REMOVE yang diterapkan langsung ke instance turunan yang terpengaruh , yang berarti bahwa operasi REMOVE selanjutnya diturunkan ke turunan bertingkat. Akibatnya, Anda harus memastikan bahwa tidak ada instance lain yang mereferensikan yang dihapus (jika tidak, akan tetap ada).

6. Penghapusan Menggunakan Pernyataan JPQL

Hibernate mendukung operasi penghapusan gaya DML:

Foo foo = new Foo("foo"); entityManager.persist(foo); flushAndClear(); entityManager.createQuery("delete from Foo where id = :id") .setParameter("id", foo.getId()) .executeUpdate(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

Penting untuk diperhatikan bahwa pernyataan JPQL gaya DML tidak memengaruhi status maupun siklus hidup instance entitas yang sudah dimuat ke dalam konteks persistensi , jadi sebaiknya dieksekusi sebelum memuat entitas yang terpengaruh.

7. Penghapusan Menggunakan Kueri Asli

Terkadang kita perlu kembali ke kueri asli untuk mencapai sesuatu yang tidak didukung oleh Hibernate atau khusus untuk vendor database. Kami juga dapat menghapus data dalam database dengan kueri asli:

Foo foo = new Foo("foo"); entityManager.persist(foo); flushAndClear(); entityManager.createNativeQuery("delete from FOO where ID = :id") .setParameter("id", foo.getId()) .executeUpdate(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

Rekomendasi yang sama berlaku untuk kueri asli seperti untuk pernyataan gaya DML JPA, yaitu kueri asli tidak memengaruhi status maupun siklus hidup instance entitas yang dimuat ke dalam konteks persistensi sebelum eksekusi kueri .

8. Penghapusan Lembut

Seringkali tidak diinginkan untuk menghapus data dari database karena tujuan audit dan penyimpanan sejarah. Dalam situasi seperti itu, kami dapat menerapkan teknik yang disebut penghapusan lunak. Pada dasarnya, kami hanya menandai baris sebagai dihapus dan kami memfilternya saat mengambil data.

In order to avoid lots of redundant conditions in where clauses in all the queries that read soft-deletable entities, Hibernate provides the @Where annotation which can be placed on an entity and which contains an SQL fragment that is automatically added to SQL queries generated for that entity.

To demonstrate this, we add the @Where annotation and a column named DELETED to the Foo entity:

@Entity @Where(clause = "DELETED = 0") public class Foo { // other mappings @Column(name = "DELETED") private Integer deleted = 0; // getters and setters public void setDeleted() { this.deleted = 1; } }

The following test confirms that everything works as expected:

Foo foo = new Foo("foo"); entityManager.persist(foo); flushAndClear(); foo = entityManager.find(Foo.class, foo.getId()); foo.setDeleted(); flushAndClear(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

9. Conclusion

In this article, we looked at different ways in which data can be deleted with Hibernate. We explained basic concepts and some best practices. We also demonstrated how soft-deletes can be easily implemented with Hibernate.

Implementasi dari Menghapus Objek dengan Tutorial Hibernasi ini tersedia di Github. Ini adalah proyek berbasis Maven, jadi semestinya mudah untuk mengimpor dan menjalankan apa adanya.