-
[Spring Data JPA] Cascade와 OrphanRemovalSpring/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