1. 핵심 주제
JPA에서 부모 엔티티와 자식 엔티티의 생명주기를 관리하는 두 가지 옵션, **CascadeType**과 **orphanRemoval**의 정확한 차이와 사용법을 정리한다.
2. 가장 헷갈리는 차이점: REMOVE vs orphanRemoval
둘 다 "삭제"와 관련되어 있어서 혼동하기 쉽지만, **삭제가 발동되는 조건(Trigger)**이 다르다.
A. 비교 분석
| 구분 | CascadeType.REMOVE | orphanRemoval = true |
| 핵심 개념 | 행동 전파 (Action Propagation) | 관계 정리 (Relationship Management) |
| 발동 시점 | repo.delete(parent) 호출 시 | 1. repo.delete(parent) 호출 시 (기본 포함) 2. parent.setChild(null) 로 연결 끊을 시 |
| 비유 | 순장(殉葬) "내가 죽으면 너도 같이 죽자." |
토사구팽(兎死狗烹) "너랑 연 끊을 거니까 넌 이제 죽어라." |
| 결과 | 부모가 삭제되면 자식도 삭제됨. | 부모가 삭제되거나, 부모에게 버림받으면(참조 해제) 삭제됨. |
B. "참조 제거"란 무엇인가?
자바 코드 상에서 부모가 자식을 더 이상 참조하지 않도록 null을 넣거나 리스트에서 remove 하는 행위.
- CascadeType.ALL만 있을 때:
- member.setProfile(null); -> DB 삭제 안 됨.
- 연결 고리(FK)만 끊기고 데이터는 DB에 미아처럼 남음 (쓰레기 데이터).
- orphanRemoval = true가 있을 때:
- member.setProfile(null); -> DB 자동 삭제 (DELETE 쿼리 전송).
- 주인 없는 고아 객체는 가차 없이(칼같이) 삭제해버림.
3. CascadeType의 종류 (영속성 전이)
Cascade는 부모가 겪는 상황을 자식에게 "전파"하는 옵션이다.
- ALL: 모든 전이 기능 포함 (가장 많이 사용)
- PERSIST: 저장(save) 전파. (부모 저장하면 자식도 자동 저장)
- REMOVE: 삭제(delete) 전파.
- MERGE: 병합(merge) 전파. (수정 사항 반영)
- REFRESH: 새로고침 전파.
- DETACH: 비영속 전파. (JPA 관리 대상에서 제외)
4. 누가 부모(Parent)인가? (개념 바로잡기)
흔히 "외래키(FK)를 가진 쪽이 부모 아닌가?"라고 오해하기 쉽지만, JPA 엔티티 관계에서는 반대다.
A. 부모 (LifeCycle Owner)
- 정의: Cascade나 orphanRemoval 옵션을 설정한 쪽.
- 특징: 생명주기를 주도한다. "나 죽을 때 따라와"라고 명령하는 쪽.
- 코드 위치: 보통 mappedBy가 있는 쪽 (외래키 없음).
B. 자식 (Foreign Key Owner / 연관관계의 주인)
- 정의: @JoinColumn (외래키)을 가지고 있는 쪽.
- 특징: DB 테이블에 누가 내 주인인지 적어두는 쪽.
- 코드 위치: @JoinColumn이 있는 쪽.
C. 내 코드 예시
Java
// [부모] Member : 옵션을 설정함 (생명주기 관리자)
@OneToOne(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
private Profile profile;
// [자식] Profile : 외래키를 가짐 (연관관계 주인)
@JoinColumn(name = "user_id")
private Member member;
요약: Member(부모)가 Profile(자식)을 관리한다. Profile은 user_id라는 명찰(FK)을 달고 Member를 따라다닌다.
5. 실무 사용 가이드 (Best Practice)
언제 써야 하는가? (orphanRemoval = true)
자식 엔티티가 "특정 부모에게만 소유되는(Private Owned) 관계" 일 때.
- 게시글 - 댓글 (게시글 지워지면 댓글 의미 없음)
- 주문 - 주문상품 (주문 취소되면 상품 내역 의미 없음)
- 첨부파일 (글 지워지면 파일 의미 없음)
언제 쓰면 안 되는가?
자식 엔티티가 "여러 곳에서 공유되는 관계" 일 때.
- 회원 - 팀 (회원이 탈퇴한다고 팀을 폭파시키면 안 됨)
- 상품 - 카테고리 (상품 삭제한다고 카테고리 없애면 안 됨)
6. 결론
코드를 짤 때 CascadeType.ALL과 orphanRemoval = true를 함께 쓰면 부모 엔티티를 통해서 자식의 생명주기(생성, 수정, 삭제)를 완벽하게 관리할 수 있다.
Java
// Member(부모)만 건드려도 Profile(자식)이 알아서 관리됨
memberRepository.save(member); // Profile도 자동 저장 (Cascade)
memberRepository.delete(member); // Profile도 자동 삭제 (Cascade)
member.setProfile(null); // Profile 자동 삭제 (orphanRemoval)
이 두 옵션을 적절히 사용하면 자식 Repository를 따로 만들지 않아도 되어 코드가 매우 깔끔해진다. 단, 참조하는 곳이 하나일 때만 사용하자!
'Archive > TIL' 카테고리의 다른 글
| [TIL] 지저분한 Service 코드, 정적 팩토리 메서드로 깔끔하게 정리하기 (0) | 2025.12.30 |
|---|---|
| [TIL] 웹 데이터의 변신과 여행: 직렬화부터 JWT 저장까지 (0) | 2025.12.29 |
| [TIL] REST API 인증과 상태 관리: Session vs Token 정리 (0) | 2025.12.09 |
| [TIL] 리액트 핵심 이론 정리: 제이쿼리 차이, 클로저, 비동기 (0) | 2025.12.05 |
| [TIL] 리액트 라우팅과 전역 상태 관리: Router & Context API (1) | 2025.12.02 |