
영속성 전이: CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 transitive persistence 기능을 사용하면 된다.
JPA는 CASCADE 옵션으로 영속성 전이를 제공한다. 쉽게 말해서 영속성 전이를 사용하면 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장할 수 있다. 예제로 알아보자.
"부모 엔티티는 여러 자식 엔티티를 가지고 있다."라는 것을 엔티티로 표현해보고, 자식 엔티티를 부모에게 할당해보자.
부모 엔티티
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
@OneToMany (mappedBy = "parent")
private List<Child> children = new ArrayList<Child>();
}
자식 엔티티
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
@ManyToOne
private Parent parent;
}
부모 1명에 자식 2명을 저장하는 코드
private static void saveNoCascade (EntityManager em) {
//부모 저장
Parent parent = new Parent();
em.persist (parent);
//1번 자식 저장
Child child1 = new Child();
child1.setParent(parent); //자식 > 부모 연관관계 설정
parent.getChildren().add(child1); // 부모 자식
em.persist(child1);
//2번 자식 저장
Child child2 new Child();
child2.setParent(parent); //자식 > 부모 연관관계 설정
parent.getChildren().add(child2); // 부모 자식
em.persist (child2);
}
JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다. 따라서 예제를 보면 부모 엔티티를 영속 상태로 만들고 자식 엔티티도 각각 영속 상태로 만든다.
이럴 때 영속성 전이를 사용하면 부모만 영속 상태로 만들면 연관된 자식까 지 한 번에 영속 상태로 만들 수 있다.
CASCADE 활용
영속성 전이를 활성화하는 CASCADE 옵션을 적용해보자.
@Entity
public class Parent {
@OneToMany (mappedBy = "parent", cascade = CascadeType. PERSIST)
private List<Child> children = new ArrayList<Child>();
}
부모를 영속화할 때 연관된 자식들도 함께 영속화하기 위해 cascade = CascadeType.PERSIST 옵션을 설정했다. 이 옵션을 적용하면 em.persist(parent) 한번으로 간편하게 부모와 연관된 자식 엔티티를 한 번에 영속화할 수 있다.
CASCADE 저장 코드
private static void saveWithCascade (EntityManager em) {
Child child1 = new Child();
Child child2 = new Child ();
Parent parent = new Parent();
child1.setParent(parent); // 연관관계 추가
child2.setParent(parent); // 연관관계 추가
parent.getChildren().add(child);
parent.getChildren().add(child2);
//부모 저장, 연관된 자식들 저장
em.persist (parent);
}
부모만 영속화하면 CascadeType. PERSIST로 설정한자식 엔티티까지 함께 영속화해서 저장한다.
영속성 전이는 연관관계를 매핑하는 것과는 아무 관련이 없다. 단지 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공할 뿐이다. 그래서 양방향 연관관계를 추가한 다음 영속 상태로 만든 것을 확인할 수 있다.
CASCADE 종류
JPA에서 `CascadeType`은 엔티티의 상태 변화가 연관된 엔티티에 어떻게 전파되는지를 정의한다.
주요 `CascadeType`의 종류는 다음과 같다.
- CascadeType.ALL: 모든 상태 변화를 전파합니다. (Persist, Merge, Remove, Refresh, Detach)
- CascadeType.PERSIST: 엔티티를 저장할 때 연관된 엔티티도 함께 저장합니다.
- CascadeType.MERGE: 엔티티를 병합할 때 연관된 엔티티도 함께 병합합니다.
- CascadeType.REMOVE: 엔티티를 삭제할 때 연관된 엔티티도 함께 삭제합니다.
- CascadeType.REFRESH: 엔티티를 새로고침할 때 연관된 엔티티도 함께 새로고침합니다.
- CascadeType.DETACH: 엔티티를 분리할 때 연관된 엔티티도 함께 분리합니다.
고아 객체
JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데 이것을 고아 객체(ORPHAN) 제거라 한다. 이 기능을 사용해서 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제되도록 해보자.
고아 객체 제거 기능 설정
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
@OneToMany (mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<Child>();
}
프록시와 연관관계 관리
고아 객체 제거 기능을 활성화하기 위해 컬렉션에 orphanRemoval = true를 설정하자. 이제 컬렉션에서 제거한 엔티티는 자동으로 삭제된다. 다음 사용 코드를 보자.
Parent parent1 = em.find(Parent.class, id);
parent.getChildren().remove(0); //자식 엔티티를 컬렉션에서 제거
실행 결과 SQL은 다음과 같다.
DELETE FROM CHILD WHERE ID=?
사용 코드를 보면 컬렉션에서 첫 번째 자식을 제거했다. orphanRemoval = true 옵션으로 인해 컬렉션에서 엔티티를 제거하면 데이터베이스의 데이터도 삭제된다. 고아 객체 제거 기능은 영속성 컨텍스트를 플러시할 때 적용되므로 플러시 시점에 DELETE SQL이 실행된다. 모든 자식 엔티티를 제거하려면 다음 코드처럼 컬렉션을 비우면 된다.
parentl.getChildren() .clear();
고아 객체를 정리해보자. 고아 객체 제거는 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다. 따라서 이 기능은 참조하는 곳이 하나일 때만 사용해야 한다. 쉽게 이야기해서 특정 엔티티가 개인 소유하는 엔티티에만 이 기능을 적용해야 한다.
만약 삭제한 엔티티를 다른 곳에서도 참조한다면 문제가 발생할 수 있다. 이런 이유로 orphanRemovel은 OneToOne, @OneToMany에만 사용할 수 있다.
고아 객체 제거에는 기능이 하나 더 있는데 개념적으로 볼때 부모를 제거하면 자식은 고아가 된다. 따라서 부모를 제거하면 자식도 같이 제거된다. 이것은 CascadeType.REMOVE를 설정한 것과 같다.
orphanRemoval = true VS CascadeType.Delete
orphanRemoval = true와 CascadeType.Delete에 대해서 헷갈릴텐데, Team과 Member의 관계를 생각해보면 상황에 따라 어떤 옵션을 사용해야할지 더 명확해진다.
비즈니스 요구사항에 따라 달라지는데,
Team의 Member Field에 orphanRemoval = true를 사용해야 하는 경우
- Team과 Member의 관계에서 Team은 일대다 관계에서 일에 속한다. → 참조하는 곳이 하나일 때만 사용해야 한다.
- Team에 속해있지 않은 Member 는 존재할 수 없다. → 컬렉션에서 엔티티를 제거하면 데이터베이스의 데이터도 삭제된다.
- Team에 속해있는 Member는 Team이 사라지면 같이 사라진다 → 부모를 제거하면 자식은 고아가 된다. 따라서 부모를 제거하면 자식도 같이 제거된다. 이것은 CascadeType.REMOVE를 설정한 것과 같다.
Team의 Member Field에 CascadeType.Delete를 사용해야 하는 경우
- Member는 Team 뿐만 아니라 다른 엔티티에도 속할 수 있다.
- Team에 속해있지 않은 Member 가 존재할 수 있다.
- Team에 속해있는 Member는 Team이 사라지면 같이 사라진다
정리하자면, orphanRemoval = true는 부모를 제거하면 자식도 같이 제거되는 CascadeType.Delete 기능을 포함하고 있지만, OneToOne, @OneToMany과 같은 관계에서만 설정할 수 있다.
출처
김영한, "자바 ORM 표준 JPA 프로그래밍(2015)", 에이콘출판사
'Database' 카테고리의 다른 글
QueryDSL로 원하는 데이터만 추출하기 - Projection (1) | 2024.12.08 |
---|---|
QueryDsl 소개 (0) | 2024.11.26 |
JPA 고급 - 지연로딩의 활용 (0) | 2024.08.02 |
JPA 고급 - Fetch 전략 Eager, Lazy (0) | 2024.08.01 |
JPA - 프록시 개념과 활용 방법 (0) | 2024.07.28 |

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!