Gambaran Umum Jenis JPA / Hibernate Cascade

1. Perkenalan

Dalam tutorial ini, kita akan membahas apa itu cascading di JPA / Hibernate. Kemudian kita akan membahas berbagai jenis kaskade yang tersedia, bersama dengan semantiknya.

2. Apa itu Cascading?

Hubungan entitas sering kali bergantung pada keberadaan entitas lain - misalnya, hubungan Person - Alamat . Tanpa Orang , entitas Alamat tidak memiliki arti sendiri. Saat kami menghapus entitas Person , entitas Alamat kami juga harus dihapus.

Cascading adalah cara untuk mencapai ini. Saat kami melakukan beberapa tindakan pada entitas target, tindakan yang sama akan diterapkan ke entitas terkait.

2.1. Jenis Kaskade JPA

Semua operasi kaskade khusus JPA diwakili oleh enum javax.persistence.CascadeType yang berisi entri:

  • SEMUA
  • PERSIST
  • MENGGABUNGKAN
  • MENGHAPUS
  • MENYEGARKAN
  • MELEPASKAN

2.2. Jenis Hibernate Cascade

Hibernate mendukung tiga Jenis Kaskade tambahan bersama dengan yang ditentukan oleh JPA. Jenis Kaskade khusus Hibernasi ini tersedia di org.hibernate.annotations.CascadeType :

  • MENGULANGI
  • SAVE_UPDATE
  • MENGUNCI

3. Perbedaan Antara Jenis Kaskade

3.1. CascadeType . SEMUA

Cascade.ALL menyebarkan semua operasi - termasuk operasi khusus Hibernate - dari induk ke entitas anak.

Mari kita lihat sebagai contoh:

@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String name; @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private List addresses; }

Perhatikan bahwa dalam asosiasi OneToMany , kami telah menyebutkan tipe kaskade dalam anotasi.

Sekarang, mari kita lihat Alamat entitas terkait :

@Entity public class Address { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String street; private int houseNumber; private String city; private int zipCode; @ManyToOne(fetch = FetchType.LAZY) private Person person; }

3.2. CascadeType . PERSIST

Operasi persist membuat instance sementara tetap ada. CascadeType PERSIST menyebarkan operasi persist dari induk ke entitas anak . Saat kami menyimpan entitas orang , entitas alamat juga akan disimpan.

Mari kita lihat kasus uji untuk operasi persisten:

@Test public void whenParentSavedThenChildSaved() { Person person = new Person(); Address address = new Address(); address.setPerson(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); session.clear(); }

Saat kami menjalankan kasus uji di atas, kami akan melihat SQL berikut:

Hibernate: insert into Person (name, id) values (?, ?) Hibernate: insert into Address ( city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)

3.3. CascadeType . MENGGABUNGKAN

Operasi penggabungan menyalin status objek yang diberikan ke objek persisten dengan pengenal yang sama. CascadeType.MERGE menyebarkan operasi penggabungan dari induk ke entitas anak .

Mari kita uji operasi penggabungan:

@Test public void whenParentSavedThenMerged() { int addressId; Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); addressId = address.getId(); session.clear(); Address savedAddressEntity = session.find(Address.class, addressId); Person savedPersonEntity = savedAddressEntity.getPerson(); savedPersonEntity.setName("devender kumar"); savedAddressEntity.setHouseNumber(24); session.merge(savedPersonEntity); session.flush(); }

Saat kami menjalankan kasus uji di atas, operasi penggabungan menghasilkan SQL berikut:

Hibernate: select address0_.id as id1_0_0_, address0_.city as city2_0_0_, address0_.houseNumber as houseNum3_0_0_, address0_.person_id as person_i6_0_0_, address0_.street as street4_0_0_, address0_.zipCode as zipCode5_0_0_ from Address address0_ where address0_.id=? Hibernate: select person0_.id as id1_1_0_, person0_.name as name2_1_0_ from Person person0_ where person0_.id=? Hibernate: update Address set city=?, houseNumber=?, person_id=?, street=?, zipCode=? where id=? Hibernate: update Person set name=? where id=?

Di sini, kita dapat melihat bahwa operasi penggabungan terlebih dahulu memuat entitas alamat dan orang, lalu memperbarui keduanya sebagai hasil dari CascadeType MERGE .

3.4. CascadeType.REMOVE

Seperti namanya, operasi penghapusan menghapus baris yang sesuai dengan entitas dari database dan juga dari konteks yang tetap.

CascadeType.REMOVE menyebarkan operasi penghapusan dari entitas induk ke entitas anak. Mirip dengan CascadeType.REMOVE JPA , kami memiliki CascadeType.DELETE , yang khusus untuk Hibernate . Tidak ada perbedaan diantara keduanya.

Sekarang, saatnya menguji CascadeType.Remove :

@Test public void whenParentRemovedThenChildRemoved() { int personId; Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); personId = person.getId(); session.clear(); Person savedPersonEntity = session.find(Person.class, personId); session.remove(savedPersonEntity); session.flush(); }

Saat kami menjalankan kasus uji di atas, kami akan melihat SQL berikut:

Hibernate: delete from Address where id=? Hibernate: delete from Person where id=?

The alamat yang terkait dengan orang juga mendapat dihapus sebagai akibat dari CascadeType HAPUS .

3.5. CascadeType.DETACH

The detach operation removes the entity from the persistent context. When we use CascaseType.DETACH, the child entity will also get removed from the persistent context.

Let's see it in action:

@Test public void whenParentDetachedThenChildDetached() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); assertThat(session.contains(person)).isTrue(); assertThat(session.contains(address)).isTrue(); session.detach(person); assertThat(session.contains(person)).isFalse(); assertThat(session.contains(address)).isFalse(); }

Here, we can see that after detaching person, neither person nor address exists in the persistent context.

3.6. CascadeType.LOCK

Unintuitively, CascadeType.LOCK re-attaches the entity and its associated child entity with the persistent context again.

Let's see the test case to understand CascadeType.LOCK:

@Test public void whenDetachedAndLockedThenBothReattached() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); assertThat(session.contains(person)).isTrue(); assertThat(session.contains(address)).isTrue(); session.detach(person); assertThat(session.contains(person)).isFalse(); assertThat(session.contains(address)).isFalse(); session.unwrap(Session.class) .buildLockRequest(new LockOptions(LockMode.NONE)) .lock(person); assertThat(session.contains(person)).isTrue(); assertThat(session.contains(address)).isTrue(); }

As we can see, when using CascadeType.LOCK, we attached the entity person and its associated address back to the persistent context.

3.7. CascadeType.REFRESH

Refresh operations re-read the value of a given instance from the database. In some cases, we may change an instance after persisting in the database, but later we need to undo those changes.

In that kind of scenario, this may be useful. When we use this operation with CascadeType REFRESH, the child entity also gets reloaded from the database whenever the parent entity is refreshed.

For better understanding, let's see a test case for CascadeType.REFRESH:

@Test public void whenParentRefreshedThenChildRefreshed() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); person.setName("Devender Kumar"); address.setHouseNumber(24); session.refresh(person); assertThat(person.getName()).isEqualTo("devender"); assertThat(address.getHouseNumber()).isEqualTo(23); }

Here, we made some changes in the saved entities person and address. When we refresh the person entity, the address also gets refreshed.

3.8. CascadeType.REPLICATE

The replicate operation is used when we have more than one data source, and we want the data in sync. With CascadeType.REPLICATE, a sync operation also propagates to child entities whenever performed on the parent entity.

Now, let's test CascadeType.REPLICATE:

@Test public void whenParentReplicatedThenChildReplicated() { Person person = buildPerson("devender"); person.setId(2); Address address = buildAddress(person); address.setId(2); person.setAddresses(Arrays.asList(address)); session.unwrap(Session.class).replicate(person, ReplicationMode.OVERWRITE); session.flush(); assertThat(person.getId()).isEqualTo(2); assertThat(address.getId()).isEqualTo(2); }

Because of CascadeTypeREPLICATE, when we replicate the person entity, then its associated address also gets replicated with the identifier we set.

3.9. CascadeType.SAVE_UPDATE

CascadeType.SAVE_UPDATE propagates the same operation to the associated child entity. It's useful when we use Hibernate-specific operations like save, update, and saveOrUpdate.

Let's see CascadeType.SAVE_UPDATE in action:

@Test public void whenParentSavedThenChildSaved() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.saveOrUpdate(person); session.flush(); }

Karena CascadeType.SAVE_UPDATE , ketika kita menjalankan kasus uji di atas, maka kita dapat melihat bahwa orang dan alamat keduanya telah disimpan. Inilah SQL yang dihasilkan:

Hibernate: insert into Person (name, id) values (?, ?) Hibernate: insert into Address ( city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)

4. Kesimpulan

Pada artikel ini, kita membahas berjenjang dan opsi tipe kaskade berbeda yang tersedia di JPA dan Hibernate.

Kode sumber untuk artikel tersebut tersedia di GitHub.