221202 TIL 사용자 강제 탈퇴시키기

어제 구현하지 못해 오늘로 미뤄진 작업인 관리자 페이지에서 사용자를 강제 탈퇴하는 기능을 구현해봤다.

사용자를 DB에서 지우기 위해서는 해당 사용자의 아이디를 칼럼으로 갖고 있는 Entity도 같이 제거해줘야 했다.

사용자가 작성한 게시글, 댓글, 대댓글이 있는데 여기서도 게시글과 댓글, 대댓글 사이에 연관관계가 맺어져 있어 지우는 순서도 중요했다.

대댓글이 댓글의 아이디를 갖고 있고 댓글이 게시글의 아이디를 갖고 있기 때문에 지우는 순서를 대댓글 -> 댓글 -> 게시글 순으로 지운 다음 사용자를 제거해야 했다.

 

그런데 위와 같은 과정을 거치지 않고 Entity를 지울때 연관관계에 있는 모든 것들을 한 번에 같이 지우는 방법이 JPA에 존재하지 않을까 생각해서 검색을 해봤다.

역시 특정 entity를 지울때 연관관계에 있는 entity를 같이 제거하는 방법이 있긴 있는데 @OneToMany 같은 연관관계를 설정해주는 어노테이션을 사용해야만 적용할 수 있는 방법들 뿐이었다.

첫 번째로 OneToMany 또는 OneToOne 어노테이션에 orphanRemoval = true로 설정해주면 연관관계에 있는 Entity를 알아서 제거해주는 방법이 있다.

두 번째 방법은 cascade = CascadeType.REMOVE으로 설정해주면 위와 동일하게 연관관계에 있는 entity를 삭제해준다

 

하지만 위와 같은 방법은 @OneToMany 같은 연관관계 매핑해주는 어노테이션을 사용하지 않았기 때문에 적용할 수 없는 방법이라 연관관계에 있는 Entity를 하나씩 순서에 맞게 Entity를 제거해주는 작업을 해야 했다.

 

사용자를 제거하는 로직의 순서는 아래와 같다.

 

먼저 대댓글부터 제거하는 코드이다.

private void deleteRecomments(Long userId) {
    if(recommentRepository.existsByUserId(userId)) {
        List<Recomment> recomments = recommentRepository.findAllByUserId(userId);

        recommentRepository.deleteAll(recomments);
    }
}

제거할 사용자의 아이디를 파라미터로 전달받아 사용자가 작성한 대댓글이 있는지 검사하고 있으면 사용자가 작성한 대댓글을 찾아 list에 담고 deletAll을 이용해 모두 제거한다.

 

댓글을 제거하는 코드

private void deleteComments(Long userId) {
    if(commentRepository.existsByUserId(userId)) {
        List<Comment> comments = commentRepository.findAllByUserId(userId);

        commentRepository.deleteAll(comments);
    }
}

댓글을 제거하는 코드도 대댓글을 제거하는 로직과 일치하다.

 

게시글을 제거하는 코드

private void deletePosts(Long userId) {
    if(postRepository.existsByUserId(new UserId(userId))) {
        List<Post> posts = postRepository.findAllByUserId(new UserId(userId));

        postRepository.deleteAll(posts);
    }
}

게시글을 제거하는 코드도 동일하다.

 

일단 대댓글 -> 댓글 -> 게시글 순서대로 제거하고 사용자를 제거하면 강제 탈퇴 기능이 구현이 된다.

그런데 강제 탈퇴 기능이 잘 구현되었는지 테스트 도중 한 가지 문제가 발생했다. 자신이 작성한 게시글 안에 다른 사용자가 작성한 댓글이나 대댓글이 있을 시 게시글 삭제가 되지 않았다.

당연히도 다른 사용자가 작성한 댓글 Entity안에 해당 게시글의 아이디가 포함되어있기 때문이다. 위에서 댓글을 지운 코드는 내가 작성한 댓글을 모두 지운 것이지 게시글안의 다른 사용자가 작성한 댓글을 지운것이 아니었기 때문에 게시글 안에 다른 사용자가 작성한 댓글과 대댓글을 모두 지우는 로직이 필요했었다.

 

우선 게시글의 아이디로 댓글을 지우고 댓글의 아이디로 대댓글을 지워야 했기 때문에 로직을 아래와 같이 작성했다.

 

위에서 게시글을 지우는 코드에서 deleteCommentsByPostId 메서드를 추가해주었다.

private void deletePosts(Long userId) {
    if(postRepository.existsByUserId(new UserId(userId))) {
        List<Post> posts = postRepository.findAllByUserId(new UserId(userId));

        deleteCommentsByPostId(posts);

        postRepository.deleteAll(posts);
    }
}

 

deleteCommentsByPostId메서드는 아래와 같다.

private void deleteCommentsByPostId(List<Post> posts) {
    posts.forEach(post -> {
        if(commentRepository.existsByPostId(post.id())) {
            List<Comment> comments = commentRepository.findAllByPostId(post.id());

            deleteRecommentsByCommentId(comments);

            commentRepository.deleteAll(comments);
        }
    });
}

파라미터로 게시글 리스트가 전달되었기 때문에 forEach문으로 게시글 하나하나 댓글과 대댓글을 모두 지운다.

 

게시글의 아이디를 이용해 댓글을 찾는데 댓글이 존재할 경우 댓글을 지운다. 댓글을 지우기 이전에 대댓글을 지우는 코드인 deleteRecommentsByCommentId 메서드를 추가했다. 대댓글이 댓글의 아이디를 갖고 있기 때문에 대댓글을 먼지 지워야 댓글이 지워지기 때문이다.

 

대댓글을 지우는 코드는 아래와 같다. 

private void deleteRecommentsByCommentId(List<Comment> comments) {
    comments.forEach(comment -> {
        if(recommentRepository.existsByCommentId(comment.id())) {
            List<Recomment> recomments = recommentRepository.findAllByCommentId(comment.id());

            recommentRepository.deleteAll(recomments);
        }
    });
}

대댓글을 지우는 코드도 댓글을 지우는 코드와 똑같다. 우선 댓글의 아이디로 대댓글이 존재하는지 검사하고 있으면 찾아 모든 대댓글을 지운다. 

위와 같은 과정을 거치고 나면 게시글 안에 존재하는 다른 사용자가 작성한 댓글과 대댓글 모두 지워져서 게시글을 완벽히 지울 수 있다.

이제는 사용자를 완전히 제거할 수 있게 되어 강제 탈퇴 기능도 구현했다.

 

사용자 한명을 제거하기 위한 로직이 생각보다 복잡한것 같지만 현재로서는 최선의 코드를 작성한 것 같다. 

더 나은 코드에 대해서는 조금 더 고민을 해보자!