Cascade (영속성 전이)

Soobin Kim·2025년 2월 11일

JPA

목록 보기
3/3

✏️    영속성 전이(Cascade)와 고아 객체 제거(orphanRemoval) 두 옵션에 대해 공부하면서 이 두 옵션이 실제로 어떻게 차이가 나는지 구체적으로 알게 되었다. 이에 대한 이해를 바탕으로 더 깊이 알아보고자 Cascade의 사용 위치, 조건, 옵션 종류, 그리고 orphanRemoval과의 차이점을 예시 코드와 함께 정리해보고자 한다.


✏️ 내용

1. 영속성 전이(Cascade)

영속성 전이는 객체 간의 관계에서 부모 엔티티의 상태 변화를 자식 엔티티에 전이하는 메커니즘이다. 이는 JPA에서 제공하는 기능으로, 특정 엔티티에 대한 작업이 연관된 다른 엔티티에도 자동으로 적용되도록 한다.


2. Cascade 옵션

사용 위치

  • 영속성 전이는 연관관계의 주인 반대편, 즉 부모 엔티티(다대일 관계에서 일 쪽)에서 설정하게 된다.
  • 예컨대 주문(Order)과 주문 항목(OrderItem)의 관계에서는 주문 쪽에 설정할 수 있다.
  • 이는 비즈니스 로직의 일관성을 유지하고 데이터의 정합성을 보장하는데 중요한 역할을 한다.

사용 조건

  • 영속성 전이를 사용할 때는 양쪽 엔티티의 라이프사이클이 동일하거나 비슷해야 한다.
  • 예를 들어, 주문이 삭제되면 해당 주문 항목도 함께 삭제되어야 한다.
  • 또한, 대상 엔티티로의 영속성 전이는 현재 엔티티에서만 전이되어야 하며, 다른 곳에서 전이를 설정하면 안 된다.
  • 이는 데이터의 일관성과 무결성을 유지하는데 매우 중요하다.

옵션 종류

영속성 전이의 옵션 종류는 다음과 같다:

public enum CascadeType {
    ALL,      // Cascade all operations
    PERSIST,  // Cascade persist operation
    MERGE,    // Cascade merge operation
    REMOVE,   // Cascade remove operation
    REFRESH,  // Cascade refresh operation
    DETACH    // Cascade detach operation
}
  • ALL: 모든 상태 변화를 전이한다. 부모 엔티티의 모든 상태 변화가 자식 엔티티에도 적용된다.
  • PERSIST: 저장 상태만 전이한다. 부모 엔티티가 저장될 때 자식 엔티티도 함께 저장된다.
  • REMOVE: 삭제 상태만 전이한다. 부모 엔티티가 삭제될 때 자식 엔티티도 함께 삭제된다.
  • MERGE: 업데이트 상태만 전이한다. 부모 엔티티가 병합될 때 자식 엔티티도 함께 병합된다.
  • REFRESH: 갱신 상태만 전이한다. 부모 엔티티가 새로고침될 때 자식 엔티티도 함께 새로고침된다.
  • DETACH: 영속성 컨텍스트에서 분리될 때 자식 엔티티도 함께 분리된다.

3. orphanRemoval (고아 객체 제거) 옵션

  • 고아 객체 제거는 부모 엔티티에서 사용되며, 일반적으로 @OneToMany 또는 @OneToOne 관계에서 사용된다.

  • 이는 Cascade.REMOVE와 유사하게 삭제를 전파하는 기능이다.

  • 부모 객체에서 리스트 요소를 삭제하면 해당 자식 객체는 매핑 정보가 없어지므로 자동으로 삭제된다.

  • 고아 객체 제거는 참조가 제거된 엔티티를 자동으로 삭제하므로, 해당 엔티티가 다른 곳에서 참조되지 않는 것이 보장되어야 한다.

    @Entity
    public class Order {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @OneToMany(mappedBy = "order", cascade = {CascadeType.ALL}, orphanRemoval = true)
        private List<OrderItem> items = new ArrayList<>();
        // getters, setters, etc.
    }
    
    @Entity
    public class OrderItem {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @ManyToOne
        @JoinColumn(name = "order_id")
        private Order order;
        // getters, setters, etc.
    }
    // 영속성 전이와 고아 객체 제거 예시
    Order order = new Order();
    OrderItem item1 = new OrderItem();
    OrderItem item2 = new OrderItem();
    
    order.getItems().add(item1);
    order.getItems().add(item2);
    
    // 주문 저장 (Cascade.PERSIST)
    entityManager.persist(order);
    
    // 주문 항목 삭제
    order.getItems().remove(item1); // item1이 삭제됨 (orphanRemoval=true)

옵션

  • true: 고아 객체 제거 활성화
  • false: 고아 객체 제거 비활성화 (기본값)

4. Cascade.REMOVE와 orphanRemoval의 차이점

Cascade.REMOVE는 부모 엔티티를 em.remove로 직접 삭제할 때 자식 엔티티가 삭제되는 반면, orphanRemoval은 부모 엔티티의 컬렉션에서 자식 엔티티를 제거하거나 참조를 null로 설정할 때도 자식 엔티티가 자동으로 삭제되는 더 강력한 기능을 제공한다. 이러한 차이는 도메인 모델의 일관성을 유지하는데 매우 중요한 역할을 한다.


5. orphanRemoval=true + Cascade.ALL 조합

위와 같은 옵션의 특징을 고려해봤을 때 부모-자식 관계에서 orphanRemoval=true + Cascade.ALL 조합의 사용이 권장된다고 할 수 있다. 이는 다음과 같은 명확한 이점들을 제공하기 때문이다:

  1. 완전한 생명주기 통합

    • 자식 엔티티의 라이프사이클이 부모 엔티티와 완전히 동기화된다
    • 별도의 자식 엔티티 Repository 없이도 모든 생명주기 관리가 가능하다
  2. 코드 품질 향상

    • 코드의 간결성이 향상된다
    • 유지보수가 용이해진다
    • 비즈니스 로직이 더 명확해진다
  3. 데이터 일관성 보장

    • 부모 엔티티를 통한 자식 엔티티의 통합 관리로 데이터 정합성이 향상된다
    • 고아 객체 발생을 방지하여 데이터 정합성을 유지한다
  4. 성능 최적화

    • 연관 관계의 영속성 전이가 자동으로 처리되어 불필요한 쿼리를 줄일 수 있다
    • 부모 엔티티 조작만으로 연관된 자식 엔티티들의 상태 변화를 한 번에 처리할 수 있다

✏️    결론을 내자면, 부모-자식 엔티티 관계에서 orphanRemoval=true + Cascade.ALL 조합은 매우 강력하고 유용한 도구가 된다.

  다만 이 조합을 사용할 때는 두 가지를 고려해야 한다. 첫째, 자식 엔티티가 다른 엔티티와 공유되지 않아야 하며, 둘째, 부모 엔티티의 생명주기에 자식 엔티티가 종속되어도 괜찮은 경우여야 한다.

  이러한 조건이 충족된다면, 이 조합은 코드의 간결성, 데이터 정합성, 그리고 성능 최적화까지 모두 달성할 수 있는 효과적인 방법이 될 것이다. 특히 복잡한 도메인 로직에서 엔티티 간의 관계를 명확하게 표현하고 관리해야 할 때, 이 조합의 사용을 적극적으로 고려해볼 만할 것이다.

0개의 댓글