포스트

@OnDelete vs JPA Cascade: 영속성 컨텍스트 일관성 문제

@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에 대한 참조가 없었다. 이 상황에서 상품을 삭제할 때 연관된 옵션들도 삭제하려면 어떻게 해야 할까?

해결책 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의 영속성 컨텍스트는 이 사실을 모른다(애플리케이션 메모리 상태와 DB 상태가 어긋날 수 있다)
  • 같은 트랜잭션에서 삭제된 엔티티에 접근하면 예상치 못한 동작이 발생할 수 있다

예를 들어, @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를 쓰는 방향으로 수정했다.


정리

  • DB에서 알아서 지워주면 편하겠지 싶어도, JPA를 쓰는 순간 영속성 컨텍스트라는 한 겹이 더 생긴다
  • 그래서 삭제/연관 삭제 같은 동작은 DB 한쪽에서만 처리하면 애플리케이션이 모르는 상태가 될 수 있다
  • 이 케이스에선 설정이 조금 번거롭더라도, JPA가 관리하는 방식(Cascade + orphanRemoval)으로 일관성을 가져가는 게 낫다고 판단했다

프로젝트: 카카오테크캠퍼스 3기 선물하기/주문 API 클론 코딩 관련 링크

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.