Written by : Nguyen Quoc Thai
Gmail: thaithedoublecheese1@gmail.com

1. Tổng quát

Trong hướng dẫn này, chúng ta sẽ thảo luận về phân tầng trong JPA / Hibernate. Sau đó, chúng tôi sẽ đề cập đến các loại thác khác nhau có sẵn, cùng với ý nghĩa của chúng.

2. Cascading là gì?

Mối quan hệ thực thể thường phụ thuộc vào sự tồn tại của một thực thể khác, ví dụ mối quan hệ Person - Address . Không có Person , thực thể Address không có bất kỳ ý nghĩa nào của riêng nó. Khi chúng ta xóa thực thể Person , thực thể Address của chúng ta cũng sẽ bị xóa.

Cascading là cách để đạt được điều này. Khi chúng tôi thực hiện một số hành động trên thực thể mục tiêu, hành động tương tự sẽ được áp dụng cho thực thể được liên kết.

2.1. Các kiểu Cascade JPA

Tất cả các hoạt động cascade cụ thể của JPA được biểu diễn bằng enum javax.persistence.CascadeType chứa các mục:

  • ALL
  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH

2.2. Các kiểu Cascade Hibernate

Hibernate hỗ trợ ba loại Cascade bổ sung cùng với những loại được JPA chỉ định. Các Loại Cascade dành riêng cho chế độ Hibernate này có sẵn trong org.hibernate.annotations.CascadeType:

  • REPLICATE
  • SAVE_UPDATE
  • LOCK

3. Sự khác biệt giữa các loại Cascade

3.1. CascadeType.ALL

CascadeType.ALL truyền tải tất cả các hoạt động - bao gồm cả những hoạt động dành riêng cho chế độ Hibernate - từ thực thể cha sang thực thể con.
Hãy xem nó trong một ví dụ:

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

Lưu ý rằng trong các liên kết OneToMany, chúng tôi đã đề cập đến kiểu cascade trong chú thích. Bây giờ chúng ta hãy xem thực thể Address được liên kết:

@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

Persist làm cho một phiên bản tạm thời tồn tại lâu dài. Kiểu Cascade PERSIST truyền tải hoạt động liên tục từ thực thể cha sang thực thể con. Khi chúng ta lưu thực thể Person, thực thể Address cũng sẽ được lưu.

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

Khi chúng ta chạy test case, hãy nhìn vào SQL sau đây

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

3.3. CascadeType.MERGE

Thao tác Merge sao chép trạng thái của đối tượng đã cho vào đối tượng persist có cùng định danh. CascadeType.MERGE truyền hoạt động hợp nhất từ thực thể cha sang thực thể con.
Hãy kiểm tra hoạt động hợp nhất:

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

khi chúng ta chạy test case, hoạt động của merge sẽ sinh ra câu lệnh SQL sau đây:

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=?

3.4. CascadeType.REMOVE

Như tên gọi, hoạt động remove loại bỏ hàng tương ứng với thực thể khỏi cơ sở dữ liệu và cả khỏi persistent context. CascadeType.REMOVE truyền hoạt động loại bỏ từ thực thể cha sang thực thể con. Tương tự như CascadeType.REMOVE của JPA, chúng ta có CascadeType.DELETE, dành riêng cho Hibernate. Không có sự khác biệt giữa hai.

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

khi chạy test case, chúng ta sinh ra câu lệnh SQL :

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

3.5. CascadeType.DETACH

Thao tác detach sẽ xóa thực thể khỏi persistent context. Khi chúng ta sử dụng CascadeType.DETACH, thực thể con cũng sẽ bị xóa khỏi persistent context.

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

3.6. CascadeType.LOCK

CascadeType.LOCK gắn lại thực thể và thực thể con được liên kết với persistent context một lần nữa. Hãy xem trường hợp thử nghiệm để hiểu 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();
}

Như chúng ta có thể thấy, khi sử dụng CascadeType.LOCK, chúng tta đã gắn đối tượng thực thể và địa chỉ liên kết của nó trở lạipersistent context.

3.7. CascadeType.REPLICATE

Hoạt động sao chép được sử dụng khi chúng tôi có nhiều nguồn dữ liệu và chúng tôi muốn dữ liệu được đồng bộ hóa. Với CascadeType.REPLICATE , thao tác đồng bộ hóa cũng truyền đến các thực thể con bất cứ khi nào được thực hiện trên thực thể cha.

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

Do CascadeType.REPLICATE, khi chúng tôi sao chép thực thể person, địa chỉ liên kết của nó cũng được sao chép với số nhận dạng mà chúng ta đặt.

3.8. CascadeType.SAVE_UPDATE

CascadeType.SAVE_UPDATE truyền hoạt động tương tự đến thực thể con được liên kết. Nó hữu ích khi chúng ta sử dụng các hoạt động dành riêng cho Hibernate như lưu, cập nhật và saveOrUpdate. Hãy xem CascadeType.SAVE_UPDATE hoạt động:

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

Vì CascadeType.SAVE_UPDATE, khi chúng ta chạy trường hợp thử nghiệm ở trên, chúng ta có thể thấy rằng cả PersonAddress đều đã được lưu. Đây là SQL kết quả:

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

4. Kết luận

Trong bài viết này, chúng ta đã thảo luận về khái niệm cascade và các kiểu cascade khác nhau có sẵn trong JPA và Hibernate.