영속성전이(CASCADE) 와 고아객체

KoK·2025년 7월 14일

JPA

목록 보기
8/8
post-thumbnail

1. 영속성전이란?

영속성 전이는 특정 엔티티를 영속화(persist)할 때, 연관된 다른 엔티티도 함께 영속화되도록 도와주는 기능이다.
예를 들어 TeamMember가 1:N 관계일 때, 팀을 저장할 때 연관된 멤버도 함께 저장하고 싶다면 어떻게 해야 할까?

아래는 ParentChild1:N 관계로 매핑된 엔티티 예시이다.
부모 엔티티(Parent)가 자식 엔티티(Child)를 리스트로 관리하고 있다.

@Entity
@Getter
@Setter
public class Parent {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "parent")
    private List<Child> childList = new ArrayList<>();

    public void addChild(Child child) {
        childList.add(child);
        child.setParent(this);
    }
}
@Entity
@Getter
@Setter
public class Child {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "parent_id")
    private  Parent parent;
}

기존 방식: 직접 영속화

Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);
em.persist(child1);
em.persist(child2);

tx.commit();

위 코드에서는 parent, child1, child2 를 각각 영속성 컨텍스트에 등록해야 한다.
하지만 이 관계에 cascade = CascadeType.ALL 을 설정하면, 부모 객체만 영속화 해도 자식 객체가 함께 저장된다.

Cascade 적용 방식

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();

이제 em.persist(parent) 만 호출해도 child1, child2 가 함께 저장된다.

이처럼 부모 엔티티의 상태 변경이 자식 엔티티에게 전이되는 것을
"영속성 전이(Cascade)" 라고 하며,
영속성 전이는 "연관관계 매핑" 과는 별개의 기능이며, 엔티티의 생명주기를 함께 관리하고자 할 때 주로 사용된다.
연관관계를 "어떻게 매핑하느냐" 와는 무관하며,
저장·삭제 시 편의성을 높이기 위한 별도의 기능이다.
따라서 연관관계 매핑만으로는 전이가 발생하지 않으며,
Cascade 옵션을 명시적으로 설정해야 한다.

CASCADE의 종류

  • ALL : 모두 적용
  • PERSIST : 영속
  • REMOVE : 삭제

2. 고아객체

부모 엔티티와의 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능이다.
즉, 부모 엔티티에서 자식을 컬렉션에서 제거하거나, 자식 엔티티의 부모 필드를 null로 만들면,
해당 자식은 "고아 객체" 가 되고, DB에서도 삭제된다.

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);

em.flush();
em.clear();

Parent findParent = em.find(Parent.class,parent.getId());
findParent.getChildList().remove(0);

tx.commit();

위와 같이 orphanRemoval = true 를 설정해주고 Parent 에서ChildList 의 첫번째 컬렉션을 remove 해주면 delete 쿼리가 발생한다.

실제 DB에서도 삭제된걸 확인할 수 있다.

2-1. 고아객체 주의점

1. 참조하는 곳이 하나일 때만 사용해야한다

연관관계가 끊기면 삭제되기 떄문인데, 고아 객체 제거는 부모 엔티티와 자식 엔티티의 연관관계가 끊기는 순간 DELETE 쿼리가 발생한다.

parent1.getChildren().remove(child); 
→ child와의 연관관계 끊김 → child는 orphan → 삭제

만약 여러 객체가 Child를 참조하고 있다면?

Parent1 → Child  
Parent2 → Child 

Parent1removeChild(child) 하면 child 가 삭제된다.
하지만 Parent2 입장에선 원하지 않아도 참조하던 엔티티가 삭제되버린다.
그래서 고아객체 제거는
"자식 엔티티가 오직 하나의 부모에게만 소속되어 있는 경우" 만 안전하게 사용 가능하다.

2. 왜 @OneToOne, @OneToMany만 지원할까?

위에서 설명한 내용과 이어지는 내용인데,
@ManyToOne, @ManyToMany 에서는 다대다/다대일 관계이기 때문에 고아 객체를 누군가 다른 곳에서도 참조 중일 수 있다.
예를 들어서

@ManyToMany
List<Student> students;

학생 한명이 여러 수업에 소속되어 있을 수 있다. 그런데 한 수업에서만 빠졌다고 해서 해당 학생을 DB에서 삭제 해버리면 문제가 발생한다.

참고: 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고
아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께
제거된다. 이것은 CascadeType.REMOVE 처럼 동작한다.

orphanRemoval = true 는 부모엔티티가 자식엔티티를 완전히 소유할때만 사용해야한다. 자식엔티티가 여러 곳에서 참조될 수 있는 구조에서는 연관관계가 끊긴다고 해서 바로 삭제하면 다른 참조 관계에도 영향을 주는 심각한 문제가 발생할 수 있다.
그래서 JPA는 이 기능을 @OneToOne, @OneToMany 에서만 지원하며,
자식의 생명주기를 부모가 전적으로 관리하는 경우에만 사용하는 것이 안전하다.

3. 영속성전이 + 고아객체, 생명주기

CascadeType.ALL + orphanRemoval=true

엔티티의 관계에서 cascade = CascadeType.ALLorphanRemoval = true 를 함께 사용하면
부모 엔티티 하나를 통해 자식 엔티티의 생성부터 삭제까지 생명주기를 일관성 있게 관리할 수 있다.

  • CascadeType.ALL
    부모를 persist() 하면 자식도 함께 저장되고
    부모를 remove() 하면 자식도 함께 삭제됨
    → 자식의 영속 상태 전이

  • orphanRemoval = true
    부모와의 연관관계가 끊기면 자식은 자동으로 삭제됨
    → 자식의 자동 제거

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> children = new ArrayList<>();

이렇게 설정해두면 다음과 같은 일들이 가능하다:

  • em.persist(parent) 만으로 자식까지 모두 저장
  • em.remove(parent) 만으로 자식까지 모두 삭제
  • parent.getChildren().remove(child) → DB에서 child 자동 삭제

즉, 자식은 부모의 생명주기에 완전히 의존하게 된다.

CascadeType.ALLorphanRemoval = true 를 함께 설정하면
부모 엔티티를 통해 자식 엔티티의 생명주기를 일관성 있게 관리할 수 있다.
이는 도메인 주도 설계(DDD) 에서 Aggregate Root 가 내부 구성요소의 생명주기를 책임지는 구조와 잘 맞아떨어지며,
실무에서도 자식이 부모에게 전적으로 종속되는 경우에 매우 유용하게 사용될 수 있을것 같다.

profile
개발 이것저것

0개의 댓글