JPA 연관 관계 - 영속성 전이 Cascade

목차

JPA 영속성 전이 Cascade

Cascading 이란, 특정 Entity 에 작업을 수행했을 때, 같은 작업이 연관된 Entity 에도 일어나는 것을 의미한다.

  • 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다.
  • 엔티티를 연속화 할때 연관된 엔티티도 함께 영속화 하는 편리함을 제공한다.

영속성 전이 옵션

CascadeType.ALL 상위 Entity 에서 발생한 모든 작업을 하위 Entity 로 모두 전파
CascadeType.PERSIST 상위 Entity 를 영속화 하는 작업이 일어 날때 하위 Entity 도 같이 영속화 한다.
CascadeType.MERGE 상위 Entity 에서 발생한 Merge 작업을 하위 Entity 까지 Merge 작업을 전파한다.
CascadeType.REMOVE 상위 Entity 를 삭제할 때 하위 Entity 까지 삭제한다.
CascadeType.REFRESH DataBase 로부터 상위 Entity 를 다시 읽어올때 하위 Entity 까지 다시 읽어온다.
CascadeType.DETACH 상위 Entity 를 영속성 컨텍스트에서 제거할 때 하위 Entity 까지 같이 제거한다.
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
public class Person {

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private List<Phone> phones = new ArrayList<>();

public void addPhone(Phone phone){
phones.add(phone);
phone.setPerson(this);
}
}
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class Phone {

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;

public String phoneNumber;

@ManyToOne
@JoinColumn(name = "PERSON_ID")
public Person person;
}

1. CascadeType.ALL

상위 Entity 에서 발생한 모든 작업을 하위 Entity 로 모두 전파한다.

@Test
void cascadeAllTest() {
Phone phone = new Phone();
phone.setPhoneNumber("010-1234-1234");

Person person = new Person();
person.setName("tester");

person.addPhone(phone);
entityManager.persist(person);
entityManager.flush();
}
Hibernate: insert into person (id, name) values (null, ?)
Hibernate: insert into phone (id, person_id, phone_number) values (null, ?, ?)

2. CascadeType.PERSIST

상위 Entity 를 영속화 하는 작업이 일어 날때 하위 Entity 도 같이 영속화 한다.

@Test
void cascadePersistTest() {
Phone phone = new Phone();
phone.setPhoneNumber("010-1234-1234");

Person person = new Person();
person.setName("tester");

person.addPhone(phone);
entityManager.persist(person);
entityManager.flush();

entityManager.remove(person); // 만약 지우게 될 경우 예외가 발생하게 된다.
}
Hibernate: insert into person (id, name) values (null, ?)
Hibernate: insert into phone (id, person_id, phone_number) values (null, ?, ?)

3. CascadeType.MERGE

상위 Entity 에서 발생한 Merge 작업을 하위 Entity 까지 Merge 작업을 전파한다.

@Test
void cascadeMergeTest() {
Phone phone = new Phone();
phone.setPhoneNumber("010-1234-1234");

Person person = new Person();
person.setName("tester");

person.addPhone(phone);
entityManager.persist(person);
entityManager.persist(phone);
entityManager.flush();

// 영속성 Context 를 초기화 한다.
entityManager.clear();

Person savedPerson = phone.getPerson();

phone.setPhoneNumber("010-1111-2222");
savedPerson.setName("tester2");

// Person 객체를 merge 할 경우 하위 Phone 객체까지 같이 영속성 Context 로 올라오게 된다.
entityManager.merge(person);
}
Hibernate: insert into person (id, name) values (null, ?)
Hibernate: insert into phone (id, person_id, phone_number) values (null, ?, ?)
Hibernate: select person0_.id as id1_0_1_, person0_.name as name2_0_1_, phones1_.person_id as person_i3_1_3_, phones1_.id as id1_1_3_, phones1_.id as id1_1_0_, phones1_.person_id as person_i3_1_0_, phones1_.phone_number as phone_nu2_1_0_ from person person0_ left outer join phone phones1_ on person0_.id=phones1_.person_id where person0_.id=?
Hibernate: update phone set person_id=?, phone_number=? where id=?
Hibernate: update person set name=? where id=?

4. CascadeType.REMOVE

상위 Entity 를 삭제할 때 하위 Entity 까지 삭제한다.

@Test
void cascadeRemoveTest() {
Phone phone = new Phone();
phone.setPhoneNumber("010-1234-1234");

Person person = new Person();
person.setName("tester");

person.addPhone(phone);
entityManager.persist(person);
entityManager.persist(phone);
entityManager.flush();
Long personId = person.getId();
entityManager.clear();

Person savedPerson = entityManager.find(Person.class, personId);
// Person 객체를 삭제할 경우 하위 Phone 객체까지 삭제된다.
entityManager.remove(savedPerson);
}
Hibernate: delete from phone where id=?
Hibernate: delete from person where id=?

5. CascadeType.REFRESH

DataBase 로부터 상위 Entity 를 다시 읽어올때 하위 Entity 까지 다시 읽어온다.

@Test
void cascadeRefreshTest() {
Phone phone = new Phone();
phone.setPhoneNumber("010-1234-1234");

Person person = new Person();
person.setName("tester");

person.addPhone(phone);
entityManager.persist(person);
entityManager.persist(phone);
entityManager.flush();

person.setName("test2");
phone.setPhoneNumber("010-1111-2222");
// Person 객체와 하위 Phone 객체까지 영속성 Context 내 데이터가 초기화 된다.
entityManager.refresh(person);

// DataBase 에 데이터가 반영되지 않은 상태에서 Refresh 작업이 일어났기 때문에 상태가 초기화 된다.
assertThat(person.getName()).isEqualTo("tester");
assertThat(phone.getPhoneNumber()).isEqualTo("010-1234-1234");
}

update 가 이뤄지지 않은 것을 확인할 수 있다.

Hibernate: insert into person (id, name) values (null, ?)
Hibernate: insert into phone (id, person_id, phone_number) values (null, ?, ?)
Hibernate: select phone0_.id as id1_1_0_, phone0_.person_id as person_i3_1_0_, phone0_.phone_number as phone_nu2_1_0_ from phone phone0_ where phone0_.id=?
Hibernate: select person0_.id as id1_0_1_, person0_.name as name2_0_1_, phones1_.person_id as person_i3_1_3_, phones1_.id as id1_1_3_, phones1_.id as id1_1_0_, phones1_.person_id as person_i3_1_0_, phones1_.phone_number as phone_nu2_1_0_ from person person0_ left outer join phone phones1_ on person0_.id=phones1_.person_id where person0_.id=?

6. CascadeType.DETACH

상위 Entity 를 영속성 컨텍스트에서 제거할 때 하위 Entity 까지 같이 제거한다.

@Test
void cascadeDetachTest() {
Phone phone = new Phone();
phone.setPhoneNumber("010-1234-1234");

Person person = new Person();
person.setName("tester");

person.addPhone(phone);
entityManager.persist(person);
entityManager.persist(phone);
entityManager.flush();

assertThat(entityManager.contains(person)).isTrue();
assertThat(entityManager.contains(phone)).isTrue();

// Person 객체가 영속성 Context 에서 detach 될때 관련된 Phone 객체도 같이 detach 된다.
entityManager.detach(person);

assertThat(entityManager.contains(person)).isFalse();
assertThat(entityManager.contains(phone)).isFalse();
}

영속성 전이 주의점

  • 소유자가 하나일때 사용하는 것이 좋다.
  • 다른 테이블과 연관관계가 있을 경우에 사용하게 되면 문제가 생길 수 있다.
Share