게시글을 여러 개 작성한 Member가 회원탈퇴를 한다고 했을 때, 처음 떠올렸던 건 엔티티 객체를 순서대로 delete해야하나? 라고 생각했지만, 뒤늦게 엔티티 필드간 연관관계를 매핑할 때 Cascade option을 지정했던 것이 떠올랐다. 마음 편하게 memberRepository.delete(member)를 작성했지만,, 실패했다. 외래키 제약조건이 위배된다는 로그메시지였다. 분명 연관관계가 걸린 필드들에서 옵션을 넣었다!
cascade = CascadeType.REMOVE
@Test
public void testMemberWithdraw(){
Member member = new Member("1234","1234","gyugyu1", MemberRole.ASSOCIATE, LocalDateTime.now(),0);
em.persist(member);
//Assertions.assertEquals(byLoginId.get(), member); //성공
Board board = new Board("hello","i am person", BoardCategory.GREETING, member, 0,0);
Board board1 = new Board("hello232323","i am person1111", BoardCategory.FREE, member, 0,0);
em.persist(board);
em.persist(board1);
em.flush();
em.clear();
memberRepository.delete(member);
em.flush();
em.clear();
List<Board> memberBoards = boardRepository.findAllByMember(member);
Assertions.assertTrue(memberBoards.isEmpty());
}
테스트 코드를 작성해서 다시 돌려보았지만, 실패였다.
delete함수가 실행되면 영속성컨텍스트 내부 쿼리 저장소에 delete query가 저장되었을 것이고, em.flush()를 통해서 db에 결과가 적용이 되어야하는데, Cannot delete or update a parent row: a foreign key constraint fails 에러메시지이다...
설마 mysql db에서 테이블이 만들어질 때, cascade 설정이 안된 채, 만들어진건가? 생각이 들었다. 왜냐하면 이번학기 데이터베이스 강의에서 cascade 옵션 지정 안해서 한 문제를 틀렸기 때문이다..
- mysql workbench에 들어가서 스키마 정보를 확인해보았다. 쿼리문은 구글 검색으로 알아내었다.

SELECT *
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'notice_board' AND TABLE_NAME = 'board' AND REFERENCED_TABLE_NAME = 'member';
이를 통해 CONSTRAINT_NAME을 알아내었고, NAME을 바탕으로 CASCADE OPTION이 설정되어있는지 확인해보았다.

SELECT *
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = 'notice_board'
AND CONSTRAINT_NAME = 'FKsds8ox89wwf6aihinar49rmfy';
역시나 설정이 안되어있었다.

ALTER TABLE board DROP FOREIGN KEY FKsds8ox89wwf6aihinar49rmfy;
ALTER TABLE board
ADD FOREIGN KEY (member_id) REFERENCES member(member_id) ON DELETE CASCADE;
다시 DELTE CASCADE 를 넣어서 외래키를 다시 걸어주었다.

그리고 다시 테스트를 진행했고, 성공하였다.
아마 처음에 엔티티를 작성할 때 CASCADE OPTION을 넣지않고 DB를 만들어놓아서, 스키마에 옵션이 설정 안된게 아닐까 라는 생각이다.. 난 CASCADE가 적용된 줄 알고 작은 실수가 큰 스노우볼을 굴렸지만, 또 하나 배우는 거라고 생각해야겠다.
라고 생각하고 스키마를 전부 날리고 application.yml의 ddl-auto: create로 변경 후 테이블을 생성해보았다.

날아가는 쿼리들을 보는데 ON DELETE CASCADE가 빠진 채로 날아가는게 보였다. 이상해서 기존 테스트 코드를 다시 실행했는데 또 delete()함수 이후에 flush()에서 또 터진 것이다.
// member class : 연관관계 주인이 아닌 클래스
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
public List<Board> boards = new ArrayList<>();
구글링을 해보니 자식에게 cascade option을 주는 것 말고 부모 필드에서 옵션을 부여하는 것이 있다고 해서 새로운 어노테이션을 붙여서 다시 스키마를 생성시켜보았다.
// board class : 연관관계 주인
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "MEMBER_ID")
@OnDelete(action = OnDeleteAction.CASCADE) // new!
private Member member;

그제야 on delete cascade 옵션이 붙은 채 외래키 제약조건을 설정하는 쿼리문을 확인할 수 있었다. 자식에게만 붙이면 되는 줄 알았는데, 이 부분은 좀 더 알아봐야할 것 같다.
회원탈퇴 과정도 잘 성공하였다!
@GetMapping("/withdraw") //회원 탈퇴 (토큰으로 사용자는 확인하니까 다른 검증 과정을 노필요)
public ResponseEntity<MemberResponseDto> withdraw(@AuthenticationPrincipal Member member){
MemberResponseDto withdrawMember = memberService.withdraw(member);
return ResponseEntity.status(HttpStatus.OK).body(withdrawMember);
}
// 회원탈퇴
public MemberResponseDto withdraw(Member member){
Member findMember = memberRepository.findByLoginId(member.getUsername()).orElseThrow(
() -> new ResourceNotFoundException("Member", "Member LoginId", member.getLoginId()));
// 삭제 순서 : 추천 -> 댓글 -> 게시글 -> 멤버
memberRepository.delete(findMember);
return MemberResponseDto.fromEntity(findMember);
}
'Backend' 카테고리의 다른 글
| [SpringBoot] 게시판 만들기 (advanced type) (5) - 검색 서비스(제목, 글쓴이, 본문 내용을 키워드로 검색) (2) | 2023.12.26 |
|---|---|
| [SpringBoot] 게시판 만들기 (advanced type) (4) - 등급 별 총 회원 수 (0) | 2023.12.25 |
| [SpringBoot] 게시판 만들기 (advanced type) (3) -카테고리별 게시판 조회하기 (1) | 2023.12.23 |
| [SpringBoot] 게시판 만들기 (advanced type) (2) - JWT 적용하기 (Springboot 3.x, Swagger 3.0) (4) | 2023.12.22 |
| [SpringBoot] 게시판 만들기 (advanced type) (0) | 2023.12.18 |