약 1시간 정도 끙끙대다 결국 풀어낸 em.remove의 비밀..! 지금 바로 공개합니다.
문제 상황
로직 설명
문제를 말씀드리기 앞서 저희 프로젝트의 User(유저)와 Suspended User(탈퇴가 유예된 유저) 간의 로직을 설명해 드리겠습니다.
우선 회원 탈퇴 요청을 하면 User 테이블의 user를 Suspended User 테이블에도 생성합니다.
그리고 7일간의 유예 기간을 주는데요, 7일간 다시 로그인(탈퇴 취소)을 하지 않으면 Suspended User 테이블과 User 테이블에서 사라짐과 동시에 회원 탈퇴가 취소됩니다.
하지만 만약 탈퇴 요청을 하고 7일 이내에 접속한다면?
Supsended User 테이블에서 삭제되고 탈퇴가 됩니다.
에러 설명
바로 여기서 에러가 발생했는데요, 회원 탈퇴 취소 요청이 들어왔을 때 Supsended User 테이블에서 user를 삭제하려고 해도 안 되는 문제였습니다.
디버거를 통해서 breakpoint를 일일이 찍어봐도 분명 제대로 재로그인한 User의 id와 같은 Suspended User의 id가 전달되는데도 불구하고 remove가 작동되지 않았습니다.
문제 원인
문제의 원인은 바로 영속성 전이(Cascade)와 관련이 있었습니다.
엔티티를 설계하면서 초반에 참고를 많이 했던
에서 cascade.All을 많은 곳에서 사용하였고, 저도 김영한 님의 jpa 관련 강의들을 들으면서 알고 있던 터라 영속성 전이의 단점은 생각을 못하고 편의성만 생각해서 User의 많은 필드에 남발하고 있었습니다.
바로 삭제가 안 됐던 이유도, Suspended User를 삭제하더라도 User -> Suspended User에 연결된 cascade가 남아있기 때문입니다.
em.remove()는 삭제하려 하고, cascade 속성은 연관관계에 객체가 남아있으니 저장하려고 해서 삭제되지 않고 남아있는 것입니다.
영속성 전이란?
영속성 전이란 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들기 위해서 사용하는 것을 말합니다.
예시로, 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장하는 것이 있습니다.
물론, JPA에만 있는 개념이 아니라 RDBMS 자체를 공부할 때 같이 배우는 개념입니다.
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)
위 코드와 같이 애노테이션으로 설정할 수 있는데요, CascadeType에도 여러 종류가 있습니다.
- ALL: 모두 적용
- PERSIST: 영속
- REMOVE: 삭제
- MERGE: 병합
- REFRESH: REFRESH
- DETACH: DETACH
영속성 전이와 고아 객체를 이용해서 부모 엔티티를 통해 자식의 생명 주기를 관리할 수 있는데요, 이를 통해서 DDD(도메인 주도 설계)를 할 때 도움을 받을 수 있습니다.
하지만 이게 현재 글의 메인 주제는 아니기 때문에 이 정도만 설명하겠습니다.
문제해결
원인을 찾는 게 항상 어렵지, 원인을 찾고 나면 해결은 항상 쉽습니다.
둘의 연관관계를 없애기 위해서 user의 suspendedUser를 삭제하면 됩니다.
public void removeSuspendedUser() {
this.suspendedUser = null;
}
결과
결론적으로 원하는 대로 잘 삭제됐습니다!
그리고 이번 에러를 통해서 CascadeType에 대해서 배우고, 이를 남발하지 않고 필요한 곳에만 사용해야겠다는 생각이 들었습니다.
추가로 김영한 님의 강의 QNA를 참고해서 cascade 속성을 사용하는 경우는
- 엔티티 간의 관계가 완전 개인 소유인 경우
- DDD의 Aggregate Root를 구현하는 경우
- 애매하는 경우는 사용 x
로 정리할 수 있을 것 같습니다.
다음 편 예고
다음은 User 간의 친구를 추천하기 위한 알고리즘을 구현하는 과정을 작성하겠습니다!
개발을 엄청 힘들게 하고 와서 글감은 많지만, 다음 주와 그다음 주는 시험기간이라 확실하게 언제 쓸지는 모르겠네요!
긴 글 읽어주셔서 감사하고, 아직 개발에 대해 모르는 것이 많으니 언제든 좋은 피드백은 환영입니다!
참고 페이지