@OnDelete vs JPA Cascade: 영속성 컨텍스트 일관성 문제
문제 상황
카카오테크캠퍼스에서 선물하기 API를 JPA로 마이그레이션하다가 상품 삭제 시 옵션도 함께 삭제해야 하는 요구사항이 있었다.
그런데 엔티티 구조가 단방향 참조였다.
1
2
3
4
5
@Entity
public class Option {
@ManyToOne(fetch = FetchType.LAZY)
private Product product; // Option → Product 방향만 참조
}
Product 엔티티에는 Option에 대한 참조가 없었다. 이 상황에서 상품을 삭제할 때 연관된 옵션들도 삭제하려면 어떻게 해야 할까?
영속성 컨텍스트란?
JPA에서 가장 중요한 개념 중 하나다. 영속성 컨텍스트는 엔티티를 영구 저장하는 환경으로, 애플리케이션과 DB 사이에서 엔티티를 관리하는 1차 캐시 역할을 한다.
핵심 기능:
- 1차 캐시: 같은 트랜잭션 내에서 동일한 엔티티를 조회하면 DB가 아닌 캐시에서 가져온다
- 변경 감지 (Dirty Checking): 엔티티 변경 사항을 자동으로 감지해서 트랜잭션 커밋 시 DB에 반영한다
- 동일성 보장: 같은 트랜잭션 내에서 같은 PK로 조회한 엔티티는 항상 같은 인스턴스다
해결책 1: @OnDelete (Hibernate)
Hibernate에서 제공하는 @OnDelete 어노테이션을 사용했다.
1
2
3
@ManyToOne(fetch = FetchType.LAZY)
@OnDelete(action = OnDeleteAction.CASCADE)
private Product product;
이렇게 하면 DB 레벨에서 ON DELETE CASCADE가 설정되어서, 상품이 삭제되면 연관된 옵션도 자동으로 삭제된다.
멘토님 피드백
그런데 멘토님한테 이런 피드백을 받았다.
“@OnDelete는 Hibernate 전용 기능이며, DB가 직접 cascade delete를 실행하기 때문에 JPA의 영속성 컨텍스트에 반영되지 않습니다. 즉, JPA의 일관성과 충돌 가능성이 있으므로 JPA cascade 방식이 더 바람직할 것 같아요.”
문제점:
@OnDelete는 DB가 직접 처리한다- JPA의 영속성 컨텍스트는 이 사실을 모른다
- 같은 트랜잭션에서 삭제된 엔티티에 접근하면 예상치 못한 동작이 발생할 수 있다
예를 들어, @OnDelete로 Option이 DB에서 삭제됐는데, 영속성 컨텍스트에는 여전히 Option이 남아 있어서 마치 존재하는 것처럼 동작할 수 있다.
해결책 2: JPA Cascade (양방향 관계 필요)
JPA의 cascade를 쓰려면 양방향 관계가 필요하다.
1
2
3
4
5
@Entity
public class Product {
@OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Option> options = new ArrayList<>();
}
이렇게 하면 JPA가 삭제를 관리해서 영속성 컨텍스트와 일관성을 유지할 수 있다. Product를 삭제할 때 JPA가 연관된 Option들도 영속성 컨텍스트에서 함께 제거한다.
트레이드오프
| 방법 | 장점 | 단점 |
|---|---|---|
| @OnDelete | 단방향 유지 가능, 설정 간단 | JPA 영속성 컨텍스트와 불일치 |
| JPA Cascade | 영속성 컨텍스트 일관성 보장 | 양방향 관계 필요 |
결국 양방향 관계를 추가해서 JPA Cascade를 쓰는 방향으로 수정했다.
배운 점
- Hibernate 전용 기능과 JPA 표준 기능은 다르다
- DB 레벨에서 처리되는 것과 애플리케이션 레벨에서 처리되는 것의 차이를 이해해야 한다
- 영속성 컨텍스트의 일관성은 JPA를 쓸 때 항상 신경 써야 하는 부분이다
카카오테크캠퍼스 3기 선물하기 API 클론 코딩 중 멘토님 피드백 정리.