Memadam Objek dengan Hibernate

1. Gambaran keseluruhan

Sebagai kerangka kerja ORM yang lengkap, Hibernate bertanggungjawab untuk pengurusan kitaran hidup objek berterusan (entiti), termasuk operasi CRUD seperti membaca , menyimpan , mengemas kini dan memadam .

Dalam artikel ini, kami meneroka pelbagai cara di mana objek dapat dihapus dari pangkalan data menggunakan Hibernate dan kami menjelaskan masalah umum dan perangkap yang mungkin terjadi.

Kami menggunakan JPA dan hanya mundur dan menggunakan Hibernate API asli untuk ciri-ciri yang tidak diseragamkan dalam JPA.

2. Kaedah Menghapus Objek yang berbeza

Objek boleh dihapuskan dalam senario berikut:

  • Dengan menggunakan EntityManager.remove
  • Apabila penghapusan dilancarkan dari keadaan entiti lain
  • Apabila pemindahan anak yatim dilaksanakan
  • Dengan melaksanakan padam kenyataan JPQL
  • Dengan melaksanakan pertanyaan asli
  • Dengan menerapkan teknik penghapusan lembut (menyaring entiti yang dihapus lembut dengan syarat dalam klausa @Where )

Dalam baki artikel, kita melihat perkara-perkara ini secara terperinci.

3. Penghapusan Menggunakan Pengurus Entiti

Penghapusan dengan EntityManager adalah kaedah paling mudah untuk membuang contoh entiti:

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 dalam artikel ini, kami menggunakan kaedah pembantu untuk membersihkan dan membersihkan konteks kegigihan apabila diperlukan:

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

Setelah memanggil kaedah EntityManager.remove , instance yang disediakan beralih ke keadaan yang dihapus dan penghapusan yang berkaitan dari pangkalan data berlaku pada flush seterusnya.

Perhatikan bahawa contoh yang dihapus akan berterusan jika operasi PERSIST diterapkan padanya . Kesalahan umum adalah mengabaikan bahawa operasi PERSIST telah diterapkan pada instance yang dihapus (biasanya, kerana ia dilancarkan dari contoh lain pada waktu flush), kerana bahagian 3.2.2 spesifikasi JPA mewajibkan bahawa contoh tersebut harus berterusan lagi dalam kes seperti itu.

Kami menggambarkannya dengan menentukan persatuan @ManyToOne dari Foo ke Bar :

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

Apabila kita menghapus instance Bar yang dirujuk oleh instance Foo yang juga dimuat dalam konteks kegigihan, instance Bar tidak akan dikeluarkan dari pangkalan data:

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

Sekiranya Bar yang dibuang dirujuk oleh Foo , operasi PERSIST dilancarkan dari Foo ke Bar kerana hubungannya ditandai dengan cascade = CascadeType.ALL dan penghapusan tidak dijadualkan. Untuk mengesahkan bahawa ini berlaku, kami mungkin mengaktifkan tahap log jejak untuk pakej org.hibernate dan mencari entri seperti penghapusan entiti yang tidak dijadualkan .

4. Penghapusan lata

Penghapusan dapat diteruskan ke entiti anak-anak apabila ibu bapa dikeluarkan:

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 bar dikeluarkan kerana penyingkiran dilancarkan dari foo , kerana persatuan dinyatakan untuk merangkumi semua operasi kitaran hidup dari Foo ke Bar .

Perhatikan bahawa ia adalah hampir sentiasa bug lata REMOVE operasi dalam @ManyToMany persatuan , kerana itu akan mencetuskan mengeluarkan contoh kanak-kanak yang boleh dikaitkan dengan keadaan ibu bapa lain. Ini juga terpakai kepada CascadeType.ALL , kerana ia bermakna bahawa semua operasi adalah untuk disebarkan, termasuk REMOVE operasi.

5. Pembuangan Anak Yatim

Arahan Anak Yatim mengisytiharkan bahawa contoh entiti yang berkaitan akan dikeluarkan apabila mereka dipisahkan dari ibu bapa, atau setara ketika ibu bapa dikeluarkan.

Kami menunjukkan ini dengan menentukan persatuan seperti 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 automatik apabila dikeluarkan dari senarai 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 yang orphanRemoval operasi adalah serupa dengan REMOVE operasi digunakan terus kepada keadaan kanak-kanak yang terjejas , yang bermaksud bahawa REMOVE operasi seterusnya disebarkan kepada kanak-kanak bersarang. Akibatnya, anda harus memastikan bahawa tidak ada kejadian lain yang merujuk yang dihapus (jika tidak, ia berterusan).

6. Penghapusan Menggunakan Penyataan JPQL

Hibernate menyokong operasi pemadaman 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 bahawa pernyataan JPQL gaya DML tidak mempengaruhi keadaan atau kitaran hayat entiti yang sudah dimuat ke dalam konteks kegigihan , jadi disarankan agar ia dilaksanakan sebelum memuatkan entiti yang terlibat.

7. Penghapusan Menggunakan Pertanyaan Asli

Kadang-kadang kita perlu kembali kepada pertanyaan asli untuk mencapai sesuatu yang tidak disokong oleh Hibernate atau khusus untuk vendor pangkalan data. Kami juga boleh menghapus data dalam pangkalan data dengan pertanyaan 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());

Cadangan yang sama berlaku untuk pertanyaan asli seperti untuk pernyataan gaya JPA DML, iaitu pertanyaan asli tidak mempengaruhi keadaan atau kitaran hayat entiti yang dimuat ke dalam konteks kegigihan sebelum pelaksanaan pertanyaan .

8. Penghapusan Lembut

Selalunya tidak wajar membuang data dari pangkalan data kerana tujuan pengauditan dan menyimpan sejarah. Dalam situasi seperti itu, kita mungkin menggunakan teknik yang disebut penghapusan lembut. Pada dasarnya, kami hanya menandakan satu baris sebagai dikeluarkan dan kami menyaringnya semasa 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.

Pelaksanaan Tutorial Menghapus Objek dengan Hibernate ini tersedia di Github. Ini adalah projek berasaskan Maven, jadi mudah untuk diimport dan dijalankan sebagaimana adanya.