[JPA] 영속성 전이(cascade)와 고아객체(orphalRemoval)란?

영속성 전이

영속성 전이는 부모 엔티티의 생명주기에 종속적인 자식 엔티티의 상태변화를 같이 처리해주는 역할을 한다.

즉, 부모 엔티티를 저장하거나 삭제 할 때, 자식 엔티티 역시 같이 저장하거나 삭제될 수 있도록 하는 것이다.

이를 통해 자식 엔티티를 추가적으로 저장, 삭제하기 위한 코드를 작성하지 않아도 된다는 장점이 있다.

( 연관관계를 매핑하는 것과는 아무 관련이 없다. )

 

사용방법

영속성 전이는 @OneToOne, @OneToMany, @, @ManyToMany 관계에서 사용할 수 있으며,

사용하는 연관관계 매핑 옵션에 cascade = CascadeType.XXX 과 같은 옵션을 설정하여 사용할 수 있다.

 

주로 사용하는 옵션은 다음과 같다.

  • CascadeType.ALL - 아래의 모든 CascadeType을 적용
  • CascadeType.PERSIST - 부모 엔티티를 저장할 때 연관된 자식 엔티티도 함께 저장 (부모와 자식 영속 상태 )
  • CascadeType.REMOVE - 부모 엔티티를 삭제할 때 연관된 자식 엔티티들도 함께 삭제

 

이제 예제를 통해 알아보자.

아래의 코드는 1 : N 의 양방향 관계를 갖는 Parent와 Child의 코드이다.

@Entity
public class Parent {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> childs = new ArrayList<>();
    
    // 양방향 매핑을 위한 편의 함수
    public void addChild(Child child) {
        childs.add(child);
        child.setParent(this);
    }
    ...
}
@Entity
public class Child {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

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

 

여기서 Parent와 Child는 1 : N의 관계이므로 하나의 Parent는 여러 Child를 가질 수 있다.

만약 2개의 Child를 갖는다면 다음과 같이 persist()를 3번 호출해 Parent와 Child 각각 영속화를 시켜줘야 한다.

하지만 영속화 전이 "cascade"를 사용하면 Parent만 persist()로 영속화를 하여도 자식도 영속화를 시켜준다.

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);

저장 결과

 

Parent에 아래의 영속화 옵션을 추가하기만 하였다.

@Entity
public class Parent {
	...
    
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> childs = new ArrayList<>();
    
	...
}

 

이번엔 Parent 엔티티만 persist() 했음에도 똑같은 결과가 나오는 것을 볼 수 있다.

Child child1 = new Child("김하늘");
Child child2 = new Child("김별");

Parent parent = new Parent("김부모");
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);


어디에 cascade를 어디서 설정해야 할까?

cascade는 부모 - 자식 관계에서 부모이거나, 연관관계의 주인이 아닌 반대편에 설정해주면된다.

 

이에 대해 쉽게 예시를 들어 알아보려 한다.

영속성 전이는 @OneToOne, @OneToMany, @, @ManyToMany 관계에서 사용할 수 있다고 했다.

그런데 @ManyToMany는 거의 사용하지 않는 연관관계 매핑이므로@OneToOne, @OneToMany에 대해서 알아보자.

 

@OneToOne

하나의 게시글을 작성할 때 하나의 이미지를 첨부하여 작성한다고 가정하여 Post 엔티티와 Image 엔티티가 있다고 해보자.

그렇다면 게시글 Post가 부모가 될 것이다.

이 경우에는 Post에 cascade를 설정하면 된다.

 

@OneToMany

일대다는 "일"이 연관관계의 주인이 되고, "다" 쪽에 외래키(FK)가 있다.

하지만 이는 비정상적 구조이므로 @ManyToOne 양방향 매핑을 이용한 경우로 예시를 들겠다. ( 자세한 내용은 아래 링크 참고 )

 

[JPA] 연관관계 매핑 총 정리

@ManyToOne - 다대일 ( N : 1) 연관관계 매핑 시 가장 많이 사용하는 매핑 방법이다. 다대일은 외래키(FK)를 갖고 있는 "다"쪽이 연관관계의 주인이 된다. 단방향 매핑 @Entity public class Member { @Id @GeneratedV

hstory0208.tistory.com

다대일은 외래키(FK)를 갖고 있는 "다"쪽이 연관관계의 주인이 된다.

하나의 팀에는 여러 멤버가 속할 수 있다고 가정하여 Team 엔티티와 Member 엔티티가 있다고 해보자.

Member가 "다" 이므로 연관관계의 주인이 되고, Team은 "일"이므로 반대편이 된다.

이 경우에 연관관계 주인의 반대편인 Team에 cascade를 설정해주면된다.

 


어떤 경우에 cascade를 사용해야할까?

부모 엔티티와 자식 엔티티 간의 라이프사이클이 같은 경우

부모 엔티티와 자식 엔티티의 상태 변화가 서로에게 영향을 주는 경우, cascade 옵션을 사용하여 상태 변화를 전파할 수 있다.

이를 통해 데이터의 일관성을 유지하고 코드의 중복을 줄일 수 있다.

 

엔티티 간의 종속성이 큰 경우

엔티티 간의 종속성이 크다면, 한 엔티티의 상태 변화가 다른 엔티티에 영향을 미칠 가능성이 높다.

이러한 경우, cascade 옵션을 사용하여 상태 변화를 자동으로 전파하도록 설정할 수 있다.

 

하지만 너무 많은 엔티티에 영향을 미치는 경우, 성능 저하의 원인이 될 수 있다.

따라서 cascade 옵션은 신중하게 사용해야 하며, 꼭 필요한 경우에만 설정하는 것이 좋다.


고아객체

고아객체라는 단어가 들어보면 좀 그렇네... 라고 생각 들겠지만 그 단어가 맞다.

우리가 아는 그 단어대로, 부모 엔티티와의 관계가 끊어지면 자동으로 삭제되는 자식 엔티티를 의미한다.

이러한 고아객체를 자동으로 삭제해주는 옵션이 "orphanRemoval=true"이다.

 

이 옵션은 @OneToOne과 @OneToMany 연관관계 매핑에 사용 가능하며 해당 속성에 넣어 사용한다.

@Entity
public class Parent {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

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

    // 양방향 매핑
    public void addChild(Child child) {
        childs.add(child);
        child.setParent(this);
    }
    ...
}

 

주의할점
  • 고아 객체 제거 기능은 연관 관계가 끊어진 객체를 자동으로 삭제하므로, 의도치 않은 삭제 작업을 수행할 수도 있다.따라서 고아 객체 삭제 기능을 사용하기 전에, 해당 기능이 필요한지 명확한 용도를 고려해야한다.
  • 참조하는 곳이 하나일 때 사용해야한다. ( 여러 엔티티와 연관관계를 갖지 않아야 한다. )