Spring/Spring Data JPA

[Spring Data JPA] Cascade와 OrphanRemoval

최블랙 2022. 1. 5. 10:51

JPA를 사용하여 Entity 클래스를 설계하다보면 @OneToMany@ManyToOne와 같은 연관 관계 매핑을 주로 사용하게 된다. 이때 cascade 옵션을 사용하여 매핑된 Entity를 함께 관리할 수 있다.

Entity Cascade

Entity cascade는 Entity의 상태 변화를 전파시키는 옵션이다. 매핑되어 있는 Entity에 대해 어느 한 쪽 Entity의 상태가 변경되었을 때 그에 따른 변화를 바인딩된 Entity에게 전파하는 것을 말한다.

 

다음과 같이 Post Entity와 Comment Entity가 OneToMany 연관 관계를 가진다고 가정해보자.

@Entity
@ToString(exclude = "commentList")
@Getter
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) //cascade 옵션 적용
    private List<Comment> commentList = new ArrayList<>();

    protected Post() {
    }

    @Builder
    public Post(String title) {
        this.title = title;
    }

    public void addComment(Comment comment) {
        this.commentList.add(comment);
    }
}
@Entity
@ToString(exclude = "post")
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;

    protected Comment() {
    }

    @Builder
    public Comment(String content, Post post) {
        this.content = content;
        this.post = post;
    }

    public Comment(Post post) {
        this.post = post;
    }
}

 

Post Entity의 commentList를 보면 cascade 옵션이 적용되어있는 것을 볼 수 있다. 이렇게 되면 Post 객체만 영속화하여도 cascade 옵션으로 인해 영속성 전이가 일어나 commentList에 저장되어 있는 Comment 객체들도 영속화가 된다.

@Test
public void cascadePersistTest() {
    // Post entity 생성
    Post post = Post.builder().title("Spring Data JPA-Cascade").build();

    // Comment entity 생성
    Comment comment = Comment.builder().content("good").post(post).build();
    Comment comment1 = Comment.builder().content("thank you :)").post(post).build();
    // post에 comment 추가
    post.addComment(comment);
    post.addComment(comment1);

    // 영속성 컨텍스트에 post entity 저장
    entityManager.persist(post);
    // 영속성 컨텍스트를 비움
    entityManager.clear();

    // post entity 조회
    Post readPost = entityManager.find(Post.class, post.getId());
    System.out.println(readPost);
}

위의 코드처럼 post만 저장하면 commentList에 포함된 comment도 함께 저장되는 것을 볼 수 있다.

 

삭제에서도 동일하다.

@Test
public void cascadeRemoveTest() {
	
    // 생략

    // 삭제할 post 조회
    Post rmPost = entityManager.find(Post.class, post.getId());
    // post entity 삭제
    entityManager.remove(rmPost);
    // commit
    entityManager.flush();
}

post entity만 삭제했음에도 post entity와 매핑된 comment entity가 함께 삭제되는 것을 볼 수 있다.

Cascade Type

Cascade type으로는 총 6가지가 있다.

 

  • CascadeType.ALL: 모든 Cascade를 적용
  • CascadeType.PERSIST: entity를 영속 상태로 만들면, 매핑된 entity도 함께 영속 상태가 됨
  • CascadeType.MERGE: 준영속 상태의 entity를 영속 상태로 만들면, 매핑된 entity도 모두 영속 상태가 됨
  • CascadeType.REMOVE: entity를 제거하면 매핑된 entity도 모두 제거
  • CascadeType.DETACH: entity를 준영속 상태로 만들면, 매핑된 entity도 준영속 상태가 되어 entity manager가 관리하지 않음
  • CascadeType.REFRESH: entity를 다시 읽어올 때, 매핑된 entity도 모두 다시 읽어옴

OrphanRemoval

orphanRemoval 옵션은 말 그대로 연관 관계가 사라진, 고아가 된 entity를 삭제하는 것을 말한다.

 

  • 특정 entity가 단일 소유할 때, 참조하는 곳이 하나일 때 사용
  • @OneToOne, @OneToMany만 사용 가능
  • ophanRemoval 옵션 사용
  • 추가적으로 부모를 제거할 때 자식도 같이 제거된다

위의 Post 클래스의 postList에 orphanRemoval=true를 추가해보자.

//cascade 옵션 적용, orphanRemoval 옵션 적용
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> commentList = new ArrayList<>();

 

post의 commentList에서 comment entity를 삭제하면 연관 관계가 사라진 comment entity가 삭제되는 것을 볼 수 있다.

@Test
public void orphanRemovalTest() {

    // 생략
	
    // post 조회
	post = entityManager.find(Post.class, post.getId());
    // post에 매핑된 comment 제거
    post.getCommentList().remove(0);
    // commit
    entityManager.flush();
}

결론

개발을 하다보면 매핑된 entity를 삭제하지 않아 이슈가 발생한 경험이 종종 있다. 이러한 문제점들을 해결하기 위해서 cascade = CascadeType.ALL과 orphanRemoval = true 옵션을 같이 사용해 부모 entity를 통해서 자식 entity의 생명 주기를 관리함으로써 실수를 최소화할 수 있을 것이다.

 

 


Reference

https://developer-hm.tistory.com/47