[JPA] CascadeType.REMOVE과 orphanRemoval=true, MySQL DELETE CASCADE 비교
ㅁ 들어가며
ㅇ JPA를 사용하면서 CascadeType.REMOVE와 orphanRemoval=true 옵션의 차이를 제대로 이해하지 못하였다.
ㅇ 두 옵션 모두 엔티티 간의 관계를 관리하는 데 사용되지만, 그 목적과 동작 방식에는 차이가 있다.
ㅇ 이 글에서는 두 옵션의 차이를 설명하고, 언제 어떤 옵션을 사용해야 하는지 알아보았다.
ㅇ [JPA] CascadeType.REMOVE vs orphanRemoval=true 차이점 알아보기을 참조하여 작성하였다.
ㅁ CascadeType.REMOVE
ㅇ 부모 엔티티가 삭제될 때, 연관된 자식 엔티티도 함께 삭제되도록 설정하는 옵션이다.
@Entity
class Parent(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
@OneToMany(mappedBy = "parent", cascade = [CascadeType.REMOVE])
val children: MutableList<Child> = mutableListOf()
)
@Entity
class Child(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
@ManyToOne
@JoinColumn(name = "parent_id")
val parent: Parent
)
ㅇ 부모 엔티티가 삭제될 때 자식 엔티티도 함께 삭제되어야 하는 경우.
ㅇ 예를 들면, 게시글을 삭제할 때 해당 게시글에 달린 댓글도 함께 삭제되어야 한다.
ㅇ Parent와 Child의 관계가 끊어진 Child는 삭제 하지 않는다.
ㅁ orphanRemoval=true
@Entity
class Parent(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
@OneToMany(mappedBy = "parent", orphanRemoval = true)
val children: MutableList<Child> = mutableListOf()
)
@Entity
class Child(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
@ManyToOne
@JoinColumn(name = "parent_id")
val parent: Parent
)
ㅇ 부모 엔티티와의 관계가 끊어진 자식 엔티티(고아 객체)를 자동으로 삭제하는 옵션이다.
ㅇ 예를 들어, 쇼핑 카트에서 상품을 제거할 때 해당 상품이 더 이상 다른 카트에 속하지 않는다면 삭제해야 한다.
ㅁ 차이점 정리
CascadeType.REMOVE:부모 엔티티가 삭제될 때, 연관된 자식 엔티티도 함께 삭제될 때 사용하지만,
orphanRemoval=true: 부모 엔티티와의 관계가 끊어진 자식 엔티티(고아 객체)를 자동으로 데이터베이스에서도 삭제된다.
두 옵션 모두 부모가 삭제되면 자식 엔티티를 삭제하지만,
CascadeType.REMOVE은 부모의 입장에서 자식을 삭제하고,
orphanRemoval=true은 자식의 입장에서 부모와 관계가 없는 고아 객체 시 삭제된다.
JPA, CascadeType.REMOVE와 MySQL의 DELETE CASCADE도 기능상 유사하지만 차이점이 있다.
ㅁ MySQL의 DELETE CASCADE
ㅇ MySQL에서 DELETE CASCADE는 부모 테이블의 레코드가 삭제될 때 연관된 자식 테이블의 레코드도 자동으로 삭제한다.
ㅇ 데이터베이스 계층에서 데이터베이스가 직접 삭제한다.
CREATE TABLE 부서
(
부서코드 INT PRIMARY KEY,
부서명 VARCHAR(20)
);
INSERT INTO 부서 VALUES (10, '영업부');
INSERT INTO 부서 VALUES (20, '기획부');
INSERT INTO 부서 VALUES (30, '개발부');
CREATE TABLE 직원
(
직원코드 INT PRIMARY KEY,
부서코드 INT,
직원명 VARCHAR(20),
FOREIGN KEY (부서코드) REFERENCES 부서 (부서코드)
ON DELETE CASCADE
);
INSERT INTO 직원 VALUES (1001, 10, '이진수');
INSERT INTO 직원 VALUES (1002, 10, '곽연경');
INSERT INTO 직원 VALUES (1003, 20, '김선길');
INSERT INTO 직원 VALUES (1004, 20, '최민수');
INSERT INTO 직원 VALUES (1005, 20, '이용갑');
INSERT INTO 직원 VALUES (1006, 30, '박종일');
INSERT INTO 직원 VALUES (1007, 30, '박미경');
ㅇ 부서와 직원 테이블과 데이터를 생성하였다.
# 부서 삭제 전 직원 현황
SELECT 부서코드, count(*) cnt
from 직원
group by 부서코드;
# 10,2
# 20,3
# 30,2
# 부서 삭제
delete from 부서 where 부서코드 = 10;
# 부서에 속한 직원 삭제 확인
SELECT 부서코드, count(*) cnt
from 직원
group by 부서코드;
# 20,3
# 30,2
ㅇ 부서를 삭제하면 관련된 직원도 함께 삭제되었다.
ㅁ 차이점 정리
ㅇ CascadeType.REMOVE와 MySQL의 DELETE CASCADE는 모두 관련 데이터를 삭제하는 기능은 같다.
ㅇ 하지만, 작동 방식과 적용 계층이 다르다.
CascadeType.REMOVE | MySQL - DELETE CASCADE | |
적용범위 | JPA 엔티티 간의 관계 | 데이터베이스 테이블 간의 관계 |
제어 | 애플리케이션에서 제어 가능 | 데이터베이스 수준에서 자동으로 동작 |
성능 | 각 엔티티에 대해 개별 DELETE 쿼리 실행 | 데이터베이스 엔진이 최적화된 방식으로 삭제 수행 |
유연성 | 비즈니스 로직에 따라 선택적으로 적용 가능 | 항상 적용되며, 예외 처리가 어려움 |
ㅇ 두 방식은 상황에 따라 적절히 선택하거나 함께 사용할 수 있다.
ㅇ 애플리케이션의 요구사항, 성능 고려사항, 데이터 일관성 등을 고려하여 적절한 방식을 선택해야 한다.