영속성 전이(CASCADE)
영속성 전이 란 특정 엔티티를 영속성(persist) 상태로 만들 때 연관된 엔티티도 함께 영속성 상태로 만드는 것을 의미합니다. JPA(Java Persistence API)는 CASCADE 옵션을 통해 영속성 전이 기능을 제공합니다.
예를 들어 Person 엔티티와 Child 엔티티가 있다고 가정하겠습니다. 두 엔티티는 서로 양방향 관계를 가집니다.
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent")
private List<Child> childList = new ArrayList<>();
}
@Entity
public class Child {
@Id @GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Parent parent;
}
Parent 엔티티는 Child 엔티티를 2명 가지고 있을 경우 다음과 같이 영속화를 진행하게 됩니다.
Parent parent = new Parent();
Child childA = new Child();
Child childB = new Child();
// 연관관계 양방향 매핑
childA.setParent(parent);
parent.getChildList().add(childA);
// 연관관계 양방향 매핑
childB.setParent(parent);
parent.getChildList().add(childB);
em.persist(parent); // parent 영속화
em.persist(childA); // child A 영속화
em.persist(childB); // child B 영속화
이처럼 각각의 엔티티를 영속성 상태로 만들어줘야 합니다. 그러나 영속성 전이를 사용하게 된다면 한 번의 영속화로 해결할 수 있습니다.
Parent 객체에 다음과 같이 영속성 전이를 설정합니다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> childList = new ArrayList<>();
영속성 전이로 parent 객체만 영속 상태로 만들어주면, 연관된 자식까지 영속 상태로 전이됩니다.
Parent parent = new Parent();
Child childA = new Child();
Child childB = new Child();
// 연관관계 양방향 매핑
childA.setParent(parent);
parent.getChildList().add(childA);
// 연관관계 양방향 매핑
childB.setParent(parent);
parent.getChildList().add(childB);
em.persist(parent); // parent만 영속화하면 child도 같이 영속화된다.
위 영속성 전이는 다음과 같이 parent 엔티티가 영속화되고, Cascade.PERSIST 설정을 통해 연관된 엔티티 또한 영속화를 진행합니다.
영속성 전이는 연관관계 매핑과 아무런 관련이 없으며, 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화해주는 편리함만 제공할 뿐입니다.
영속성 전이(CASCADE) 종류
옵션 | 기능 |
CascadeType.ALL | 모두 적용 |
CascadeType.PERSIST | 영속화 |
CascadeType.MERGE | 삭제 |
CascadeType.REMOVE | 병합 |
CascadeType.REFRESH | 데이터베이스로부터 엔티티 재조회 |
CascadeType.DETACH | 준영속화 |
영속성 전이는 참조하는 곳이 한 군데인 경우, 즉 하나의 부모만 자식들을 관리하는 경우에만 사용해야 합니다. 예를 들어 게시판과 해당 게시판의 첨부파일과 같은 관계에서 사용할 수 있습니다.
고아 객체(ORPHAN)
JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 고아(ORPHAN) 객체라 부르며, 이를 자동으로 삭제해주는 기능을 지원합니다. 즉, 부모 엔티티의 컬렉션에서 자식 엔티티의 참조가 끊어지면 해당 자식 엔티티는 삭제됩니다.
사용 방법은 다음과 같이 orphanRemoval = true를 추가해주면 됩니다.
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
}
앞서 배운 영속성 전이를 통해 부모 엔티티만 영속화하여 자식 엔티티도 영속화할 수 있도록 합니다.
Parent parent = new Parent();
em.persist(parent);
Child child1 = new Child();
child1.setParent(parent);
parent.getChildList().add(child1);
Child child2 = new Child();
child2.setParent(parent);
parent.getChildList().add(child2);
먼저 부모 엔티티를 영속화하고 이후 자식 엔티티를 추가하여도 영속성 컨텍스트에 관리되고 있기 때문에 문제없이 저장됩니다. 이후 저장된 부모 엔티티를 불러와 childList 첫 번째 인덱스에 포함된 자식 엔티티를 제거합니다.
Parent parent = em.find(Parent.class, 1L);
parent.getChildList().remove(0);
부모 엔티티의 컬렉션에서 제거한 것으로 자식 엔티티가 삭제되는 것을 확인할 수 있습니다.
Hibernate:
delete
from
child
where
id=?
🤔 만약 orphanRemoval = true 설정을 하지 않고, 부모 컬렉션에 제거가 된다면?
위와 같은 코드를 실행시켰을 때 부모 엔티티는 삭제된 엔티티를 알 수 없으나, 데이터베이스는 자식 엔티티가 삭제되지 않고 고아 객체로 남아있게 됩니다. 반대로 orphanRemoval = true 설정이 된 경우, 특정 엔티티가 개인 소유이거나, 다른 곳에 참조되지 않는지 주의해야 합니다.
🤔 orphanRemoval = true vs CascadeType.REMOVE 서로 같은 기능?
각각 설정을 따로 봤을 때, 부모 엔티티가 삭제되면 자식 엔티티 또한 같이 삭제되게 됩니다. 언뜻 보면 서로 같은 기능으로 보이기도 합니다. 그러나 두 설정은 부모-자식 관계에서 다른 목적을 가지고 있습니다.
CascadeType.REMOVE는 부모 엔티티의 삭제 작업이 자식 엔티티에 전파되는 것을 관리합니다.
orphanRemoval = true는 부모와의 관계가 끊어진 자식 엔티티를 자동으로 삭제하는 것을 관리하며 자식 엔티티를 삭제하는 것에 중점을 둡니다.
결국 CascadeType.REMOVE와 orphanRemoval = true는 함께 사용되어 부모-자식 관계의 생명주기를 관리하는 데 사용됩니다. 일반적으로 CascadeType.ALL + orphanRemoval=true을 조합하여 사용합니다.
자바 ORM 표준 JPA 프로그래밍 - 기본편 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com
'Spring > JPA' 카테고리의 다른 글
엔티티(Entity) 설계시 주의점 (1) | 2024.05.23 |
---|---|
N+1 문제 (0) | 2024.04.28 |
즉시 로딩(Eager)과 지연 로딩(Lazy) (0) | 2024.04.28 |
프록시(Proxy) (0) | 2024.04.28 |
상속관계 매핑 (0) | 2024.04.24 |