오늘 겪은 문제
회원 탈퇴 시 이와 연관된 데이터(게시글, 댓글, 좋아요 등)를 모두 삭제하도록 만들고 싶었다. 기존에 A 데이터를 삭제 시 연관된 B 데이터도 삭제 되게 하는 것은 양방향 연관관계를 맺고 CascadeType.REMOVE 라는 옵션을 통해 해결했었다.
하지만 User 클래스는 User를 조회할 때 연관된 다른 데이터들을 같이 조회할 일이 없어 양방향이 아닌 단방향 연관관계를 맺어놨고 그렇기에 CasCadeType.REMOVE 라는 옵션을 사용할 수 없었다.
처음에는 User와 연관된 모든 데이터들을 불러 객체로 만들고 jpa 를 이용해 삭제시키는 방법을 생각해봤다. 하지만 이는 User와 연관된 데이터 종류가 많아질수록 코드도 굉장히 늘어나 비효율적인 방법이라고 결론을 내리고 다른 방법을 찾아봤더니 @OnDelete(action = OnDeleteAction.CASCADE) 라는 어노테이션을 이용하면 내가 원하던 것이 가능하다고 했다.
하지만 실제로 적용을 했더니 게시글 좋아요 테이블에서 참조할 게시글이 없어지는 고아 문제가 발생했다.
내가 생각하기로는 User 데이터를 삭제하면 해당 User 가 작성한 게시글들도 삭제가 될 것이고 이 때 게시글과 게시글 좋아요 연관관계 사이에 걸어두었던 CascadeType.REMOVE 를 통해서 자연스럽게 게시글에 달린 게시글 좋아요 데이터들도 삭제될 것이라고 생각했는데 그게 아니었다.
해결 방법
기존에 CascadeType.REMOVE를 설정해주었던 연관관계들에서 @OnDelete로 변경해주었다. 이렇게 변경한 이유는
CascadeType.REMOVE 와 @OnDelete 가 동작하는 방식의 차이 때문이다.
CascadeType.REMOVE vs @OnDelete
두 가지 방식은 모두 다른 데이터에 의해서 참조되고 있는 데이터(부모 데이터)가 삭제될 시 이를 참조하고 있던 데이터(자식 데이터)가 고아가 되는 현상을 해결할 수 있는 방법이다.
하지만 해당 현상을 해결하는 방식에는 분명한 차이가 있다.
아래와 같은 연관관계가 있을 때 두 방식이 어떠한 차이가 있는 지 알아보자.
<DB 스키마>
<엔티티 연관관계>
public class Post extends Timestamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE)
private List<Comment> comments = new ArrayList<>();
}
public class Comment extends Timestamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
@ManyToOne
@JoinColumn(name = "post_id")
private Post post;
}
CascadeType.REMOVE
JPA 단에서 고아 문제를 방지하기 위해서 자식 데이터부터 삭제하는 순서로 쿼리문을 여러 개 날린다.
Post 데이터를 삭제하면 연관된 Comment 데이터부터 삭제하고 Post 데이터를 삭제하도록 쿼리를 2개 날리는 것이다.
실제로 테이블을 만드는 쿼리도 보면 따로 db 단에서 처리할 수 있도록 cascade 를 설정해주지는 않는다.
@OnDelete(action = OnDeleteAction.CASCADE)
DB 단에서 delete cascade 설정을 해줘서 하나의 삭제 쿼리만으로도 이 데이터를 참조하고 있던 모든 데이터를 삭제하는 방법이다.
public class Post extends Timestamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@OneToMany(mappedBy = "post")
private List<Comment> comments = new ArrayList<>();
}
public class Comment extends Timestamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
@ManyToOne
@JoinColumn(name = "post_id")
@OnDelete(action = OnDeleteAction.CASCADE)
private Post post;
}
마지막으로 정리하자면
CascadeType.REMOVE
: JPA 단에서 고아 문제 방지를 위해 자식 데이터부터 삭제 쿼리를 날리는 것(DB 단에는 delete cascade 옵션이 추가되지 않음)
@OnDelete(action = OnDeleteAction.CASCADE)
: DB 단에서 고아 문제 방지를 위해서 delete cascade 옵션을 걸어 부모 데이터를 삭제하는 쿼리 하나만으로도 자식 데이터까지 모두 삭제하는 것
'항해99' 카테고리의 다른 글
[TIL] DAY 34 (1) | 2023.05.07 |
---|---|
[TIL] DAY31 (0) | 2023.05.03 |
[WIL] 항해 4주차 (0) | 2023.05.01 |
[TIL] DAY 27 (0) | 2023.04.29 |
[TIL] DAY 25 (0) | 2023.04.27 |