백오피스 만들기 프로젝트 (5): 쿼리문, 데이터삭제 문제해결

김재현·2023년 12월 9일
0

TIL

목록 보기
53/88
post-thumbnail
  • 프로젝트명 : "가시죠, 백오피스 만들기 프로젝트"
  • 프로젝트 소개 : 사용자 Role에 따라 인가할 수 있는 관리자 기능을 만드는 프로젝트입니다.
  • 사용 기술: #Java #Spring Boot #JPA #MySQL #Redis
    GitHub: https://github.com/k-jaehyun/IForest.git

목표로 했던 기능(팔로우,공지글등록,회원 차단)을 모두 완수했다.
그 과정에서 시도해보고 알아갈 수 있었던 것들을 정리해본다.

쿼리문이 2번?

팔로우 기능은 팔로우 테이블을 따로 생성하여 FK로 User와 연결했다.
팔로우를 수행하는 user가 sender, 팔로우를 당하는 user가 receiver이다.

FollwService

									...
        
if (!followRepository.findBySenderIdAndReceiverId(sender.getId(),receiver.getId()).isEmpty()) {
	Follow follow = followRepository.findBySenderIdAndReceiverId(sender.getId(),receiver.getId()).orElse(null);
	followRepository.delete(follow);
	return ResponseEntity.ok().body(new CommonResponseDto("언팔로우 완료..!", HttpStatus.OK.value()));
	}
    
	Follow follow = new Follow(receiver,sender);
	followRepository.save(follow);

	return ResponseEntity.ok().body(new CommonResponseDto("팔로우 완료!", HttpStatus.OK.value()));

									...

위와 같이 코드를 만들어서 팔로우 api로 사용자가 요청을 한번 보내면 팔로우가 되고, 다시 한번 요청을 보내면 언팔로우 되게 코드를 만들었다.

그런데 언팔로우시 동일한 쿼리문이 두번 날아가는 문제가 있었다.

코드에 보이다시피 followRepository.findBySenderIdAndReceiverId 이 if문에서 한번, if문의 실행문에서 한번씩 총 2번 들어가 있어서 쿼리문이 2번 날아가도록 코드를 만들었기 때문이다.
하지만 이건 테이블의 크기가 커질수록 DB를 조회하는데 시간이 오래걸리는 등 서비스의 퀄리티에 문제가 생길 수 있는 부분이라고 생각했다.

따라서 아래와 같이 DB 조회를 1회로 변경하고 조회 후 없다면 null로 받아오는 객체를 하나 선언하여 해결했다.

									...
                                    
Follow alreadyExistingFollow = followRepository.findBySenderIdAndReceiverId(sender.getId(),receiver.getId()).orElse(null);
	if (alreadyExistingFollow!=null) {
	followRepository.delete(alreadyExistingFollow);
	return ResponseEntity.ok().body(new CommonResponseDto("언팔로우 완료..!", HttpStatus.OK.value()));
}
									...

💥팔로우가 있을 때 유저 삭제가 안되는 버그

팔로우 기능을 만들고 난 뒤, 백오피스(관리자기능)의 유저 삭제가 수행되지 않는 버그가 발생했다.
오류메세지를 살펴봤을 때, Spring이 Follow의 FK로 되어있는 User의 삭제를 거부했다는 말이었다.
이것은 부모엔터티인 User가 삭제 될 때 자식엔터티인 Follow를 어떻게 할지 연관관계 맵핑이 안되어 있었기 때문에 발생한 오류였다.

여러가지를 시도하다가 이것을 해결하는 총 3가지 방법을 찾았다.

1. 직접적 삭제

다음과 같이 User엔터티가 삭제되기 전에 미리 해당 유저가 포함되어있는 Follow 엔터티를 삭제한다.

        followRepository.deleteBySenderIdOrReceiverId(user.getId(), user.getId());
        userRepository.delete(user);
  • 하지만 이것은 쿼리문이 2번 수행되며,
  • 협력 개발자가 연관관계를 알아보기 힘들 뿐만 아니라,
  • 나만 잊는다면 추후 코드 수정시 충분히 장애가 터질 수 있는 안좋은 방법으로 판단되었다.

2. User에 연관관계 맵핑

다음과 같이 User 엔터티가 삭제될 때 Follow가 어떻게 수행할지 미리 연관관계를 넣어주는 것이다.

    @OneToMany(mappedBy = "sender",cascade = CascadeType.REMOVE)
    private List<Follow> followingList;

    @OneToMany(mappedBy = "receiver",cascade = CascadeType.REMOVE)
    private List<Follow> followedList;

쿼리문도 1번 수행되며, 협력 개발자가 연관관계를 이해 할 때 도움을 줄 수 있다.
하지만 이것도 문제가 있다.

  • 이미 만들어진 코드에 연관관계를 추가하는 것이기 때문에 이전 코드의 수정이 불가피하다.
  • 그 때문에 User 테이블을 삭제했다가 다시 만들어서 연관관계 설정을 적용시켜줘야한다.

현재는 연습 단계의 프로젝트 이기 때문에 유저 정보가 삭제되어도 괜찮지만, 만약 실무라면?
큰일이 아닐 수 없다. 혹은 유저 정보를 옮겨놓는다고 하더라도 시간과 비용 문제도 있을 것이다.

그렇다면 적합한 방법은 무엇일까?

3. Follow에 연관관계 맵핑

다음과 같이 부모엔터티인 User가 삭제 될 때, 자식엔터티인 Follow가 어떻게 수행 될지 @Ondelete 애너테이션으로 옵션을 걸어줄 수 있다.

    @ManyToOne
    @JoinColumn (name = "sender_id")
    @OnDelete(action = OnDeleteAction.CASCADE)
    private User sender;

    @ManyToOne
    @JoinColumn (name = "receiver_id")
    @OnDelete(action = OnDeleteAction.SET_NULL)
    private User receiver;

이렇게하면 User테이블에 영향이 없이, 서버를 재실행하여 Follow 테이블을 생성하는 것 만으로도 오류를 해결 할 수 있다!!

다음의 아름다운 결과물을 보자

여기서 7번 유저 삭제시 (sender로 등록되어 있으면 삭제, receiver로 등록되어 있으면 null이 된다.)


맺으며...

어떤 기능을 수행시키는 코드를 만드는 여러가지 방법이 있을 것이다.
그중에서 어떤 것 이 더 좋은 방법일까 고민함으로써 개발자는 성장해나가고 코드는 간결, 명료해지며, 모두의 비용과 시간을 절약할 수 있다.
무작정 키보드 두드리는 것 보단 팔짱을 끼고 머리를 굴려봐야겠다.


관련 포스팅

Previouse Post

Following Post

profile
I live in Seoul, Korea, Handsome

0개의 댓글