ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring Data JPA] Cascade와 OrphanRemoval
    Spring/Spring Data JPA 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

     

     

    댓글

Designed by Tistory.