1. 개요

이 예제에서는 JPA/Hibernate에서 캐스케이딩이 무엇인지 논의할 것입니다. 그런 다음 의미 체계와 함께 사용 가능한 다양한 캐스케이드 유형을 다룰 것입니다.

2. 캐스케이딩이란 무엇입니까?

엔터티 관계는 종종 다른 엔터티의 존재에 의존합니다(예: 사람주소 관계). 포함하지 않는 사람주소 개체 자체의 의미가 없습니다. Person 엔터티를 삭제하면 Address 엔터티도 삭제되어야 합니다.

캐스케이딩은 이를 달성하는 방법입니다. 대상 엔터티에 대해 일부 작업을 수행하면 연결된 엔터티에 동일한 작업이 적용됩니다.

2.1. JPA 캐스케이드 유형

모든 JPA 관련 캐스케이드 작업은 다음 항목을 포함 하는 javax.persistence.CascadeType 열거형으로 표시됩니다 .

  • 모두
  • 지속
  • 병합
  • 제거하다
  • 새로 고치다
  • 분리하다

2.2. 최대 절전 모드 캐스케이드 유형

Hibernate는 JPA에 의해 지정된 것과 함께 세 가지 추가 Cascade 유형을 지원합니다. 이러한 Hibernate 특정 Cascade 유형은 org.hibernate.annotations.CascadeType 에서 사용 가능합니다 .

  • 뒤로 젖히다
  • SAVE_UPDATE
  • 자물쇠

3. 캐스케이드 유형의 차이점

3.1. 캐스케이드 유형 . 모두

CascadeType.ALL 은 Hibernate 특정 작업을 포함하여 모든 작업을 부모에서 자식 엔터티로 전파합니다.

예를 들어 보겠습니다.

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

에 있습니다 OneToMany 협회, 우리는 어노테이션에서 캐스케이드 유형을 언급했습니다.

이제 연결된 엔터티 주소를 살펴보겠습니다 .

@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. 캐스케이드 유형 . 지속

지속 작업은 임시 인스턴스를 지속적으로 만듭니다. 캐스케이드 유형 PERSIST 는 상위 항목에서 하위 항목으로 지속 작업을 전파합니다. person 엔터티를 저장하면 주소 엔터티도 저장됩니다.

지속 작업에 대한 테스트 사례를 살펴보겠습니다.

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

위의 테스트 케이스를 실행하면 다음 SQL이 표시됩니다.

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

3.3. 캐스케이드 유형 . 병합

병합 작업은 지정된 객체의 상태를 동일한 식별자를 가진 영구 객체에 복사합니다. CascadeType.MERGE 는 병합 작업을 상위 항목에서 하위 항목으로 전파합니다.

병합 작업을 테스트해 보겠습니다.

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

테스트 케이스를 실행할 때 병합 작업은 다음 SQL을 생성합니다.

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

여기에서 병합 작업이 먼저 주소개인 엔터티를 모두 로드 한 다음 CascadeType.MERGE 의 결과로 둘 다 업데이트하는 것을 볼 수 있습니다 .

3.4. 캐스케이드 유형.REMOVE

이름에서 알 수 있듯이 제거 작업은 데이터베이스와 영구 컨텍스트에서 엔터티에 해당하는 행을 제거합니다.

CascadeType.REMOVE 는 제거 작업을 부모 엔터티에서 자식 엔터티로 전파합니다. JPA의 CascadeType.REMOVE 와 유사하게  Hibernate에 특정한 CascadeType.DELETE 가 있습니다 . 둘 사이에는 차이가 없습니다.

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

테스트 케이스를 실행하면 다음 SQL이 표시됩니다.

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

주소 와 관련된 사람은 또한의 결과로 제거되었다 CascadeType.REMOVE .

3.5. 캐스케이드 유형.DETACH

분리 작업은 영구 컨텍스트에서 엔터티를 제거합니다. CascadeType.DETACH 를 사용 하면 자식 엔터티도 영구 컨텍스트에서 제거됩니다.

행동으로 봅시다:

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

여기서 우리는 person 을 분리한 후 영구 컨텍스트에 person 이나 address 가 존재 하지 않음 을 알 수 있습니다 .

3.6. 캐스케이드 유형 . 자물쇠

직관적이지 않게 CascadeType.LOCK 은 엔터티 및 관련 자식 엔터티를 영구 컨텍스트와 다시 연결합니다.

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

우리가 볼 수 있듯이 CascadeType.LOCK을 사용할 때 엔티티 사람 과 관련 주소 를 영구 컨텍스트에 다시 연결했습니다 .

3.7. 캐스케이드 유형 . 새로 고치다

새로 고침 작업 은 데이터베이스에서 지정된 인스턴스의 값을 다시 읽습니다. 어떤 경우에는 데이터베이스에 유지한 후 인스턴스를 변경할 수 있지만 나중에 이러한 변경 사항을 실행 취소해야 합니다.

이러한 종류의 시나리오에서는 유용할 수 있습니다. Cascade Type REFRESH 와 함께 이 작업을 사용 하면 상위 엔터티가 새로 고쳐질 때마다 하위 엔터티도 데이터베이스에서 다시 로드됩니다.

더 나은 이해를 위해 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);
}

여기에서 저장된 엔티티 인 personaddress 를 일부 변경했습니다 . 우리가 새로 고침 할 때 사람의 실체는 주소 도 갱신됩니다.

3.8. 캐스케이드 유형.복제

복제 작업은 둘 이상의 데이터 소스가 있고 데이터를 동기화하려는 경우에 사용됩니다. CascadeType.REPLICATE를 사용 하면 부모 엔터티에서 수행될 때마다 동기화 작업이 자식 엔터티에도 전파됩니다.

이제 CascadeType을 테스트해 보겠습니다 . 복제 :

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

의 때문에 CascadeType . REPLICATE , person 엔터티를 복제할 때 연결된 주소 도 우리가 설정한 식별자로 복제됩니다.

3.9. 캐스케이드 유형.SAVE_UPDATE

CascadeType.SAVE_UPDATE 는 연결된 자식 엔터티에 동일한 작업을 전파합니다. save , update  및 saveOrUpdate같은 Hibernate 특정 작업을 사용할 때 유용합니다

CascadeType을 살펴보겠습니다 . SAVE_UPDATE 실행 중:

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

CascadeType.SAVE_UPDATE 로 인해 위의 테스트 케이스를 실행할 때 사람주소가 모두 저장 되었음을 알 수 있습니다 .

결과 SQL은 다음과 같습니다.

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

4. 결론

이 기사에서 우리는 JPA와 Hibernate에서 사용할 수 있는 캐스케이드와 다양한 캐스케이드 유형 옵션에 대해 논의했습니다.

기사의 소스 코드는  GitHub에서 사용할 수 있습니다 .

Persistence footer banner