본문 바로가기
Spring Boot Project/Plming

[Plming] 게시판 기능 코드 리뷰 5편 (댓글 기능)

by slchoi 2022. 4. 19.
728x90
SMALL

저번 글에서는 게시글 검색 기능 관련 코드 리뷰를 진행했다. 이번 글에서는 댓글 기능 코드를 리뷰해볼 것이다.

댓글 기능 관련 코드 폴더 구조이다.

폴더 구조

먼저 dto 패키지와 entity 패키지에 포함되어 있는 코드들을 살펴본 뒤, 기능 별로 controller -> service -> repository 순으로 살펴볼 것이다.

 

1. entity 패키지

1.1. Comment 

더보기
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "comment")
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(columnDefinition = "varchar")
    private String content;

    @Column(columnDefinition = "bigint")
    private Long parentId;

    @Column(columnDefinition = "enum")
    private char deleteYn = '0';

    @Column(columnDefinition = "datetime")
    private LocalDateTime createDate = LocalDateTime.now();

    @ManyToOne
    @JoinColumn(name = "post_id")
    private Board board;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @Builder
    public Comment(String content, Long parentId, Board board, User user) {
        this.content = content;
        this.parentId = parentId;
        this.board = board;
        this.user = user;
    }
}

 

id 댓글 id. DB에서 auto_increment로 생성한다.
content 댓글 내용을 저장한다.
parentId 댓글의 부모 댓글 id를 저장한다.

이 값이 null일 경우 댓글, null이 아닐 경우 대댓글이다.
deleteYn 댓글의 삭제 여부를 판단한다. 값이 '0' 일 경우 삭제되지 않은 댓글을, '1'일 경우 삭제된 댓글을 의미한다.
createDate 댓글이 생성된 시간을 저장한다.
board 댓글이 달린 게시글의 정보를 저장한다.
user 댓글을 단 사용자의 정보를 저장한다.

 

1.2. CommentRepository

public interface CommentRepository extends JpaRepository<Comment, Long>, CommentCustomRepository {
}
  • JPA 활용을 위해 JPARepository와 Querydsl을 사용해 구현한 CommentCustomRepository를 상속받는다.

 

2. dto 패키지

2.1. CommentOneResponseDto

@Getter
public class CommentOneResponseDto {

    private final Long id;
    private final Long postId;
    private final Long parentId;
    private final Long userId;
    private final String content;
    private final LocalDateTime createDate;

    @Builder
    public CommentOneResponseDto(Comment entity) {
        this.id = entity.getId();
        this.postId = entity.getBoard().getId();
        this.parentId = entity.getParentId();
        this.userId = entity.getUser().getId();
        this.content = entity.getContent();
        this.createDate = entity.getCreateDate();
    }
}
  • 댓글 하나만 조회할 경우 댓글 정보를 CommentOneResponseDto에 담아 반환한다.
id 댓글 id를 저장한다.
postId 댓글이 달린 게시글의 정보 중에서 게시글 id를 저장한다.
userId 댓글을 단 사용자의 정보 중에서 사용자 id를 저장한다.
parentId 댓글의 부모 댓글 id를 저장한다.
content 댓글 내용을 저장한다.
createDate 댓글이 생성된 시간을 저장한다.

 

2.2. CommentRequestDto

@Getter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@AllArgsConstructor
public class CommentRequestDto {

    private String content;
    private Long parentId;

    public Comment toEntity(Board board, User user) {
        return Comment.builder()
                .board(board)
                .user(user)
                .parentId(parentId)
                .content(content)
                .build();
    }
}
  • 댓글을 생성할 때, 댓글 정보를 CommentResquestDto 객체에 담아 전달한다.
content 생성할 댓글의 내용을 저장한다.
parentId 생성할 댓글의 부모 댓글 id를 저장한다.
toEntity CommentRequestDto 객체를 Comment 객체로 변환해주는 메서드이다.

 

2.3. ReCommentResponseDto

@Getter
public class RecommentResponseDto {

    private final Long id;
    private final Long userId;
    private final Long parentId;
    private final String content;
    private final LocalDateTime createDate;
    private final char deleteYn;

    @Builder
    public RecommentResponseDto(Comment entity, User user) {
        this.id = entity.getId();
        this.userId = user.getId();
        this.parentId = entity.getParentId();
        this.content = entity.getContent();
        this.createDate = entity.getCreateDate();
        this.deleteYn = entity.getDeleteYn();
    }
}
  • 대댓글 정보를 반환할 경우, RecommentResponseDto 객체에 정보를 저장하고 RecommentResponseDto 객체를 반환한다.
id 댓글 id를 저장한다.
userId 댓글을 작성한 사용자 id를 저장한다.
parentId 댓글의 부모 댓글의 id를 저장한다.
content 댓글의 내용을 저장한다.
createDate 댓글을 생성한 시간을 저장한다.
deleteYn 댓글의 삭제 여부를 저장한다.

 

2.4. UpdateCommentRequestDto

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UpdateCommentRequestDto {

    private String content;
    private Long commentId;
}
  • 댓글을 수정할 경우 수정할 댓글의 id와 수정할 내용을 받아와 UpdateCommentRequestDto 객체에 저장한다.

 

3. 댓글 CRUD

3.1. 댓글 CREATE

CommentApiController

@PostMapping("/posts/{id}/comments")
public ResponseEntity<Long> registerComment(@PathVariable final Long id, @CookieValue final String token ,@RequestBody final CommentRequestDto comment) {

    return ResponseEntity.status(201).body(commentService.registerComment(id, jwtTokenProvider.getUserId(token), comment));
}
  • POST "/posts/{id}/comments" 요청이 들어오면 댓글 생성 로직이 실행된다.
  • URI에서 게시글 id 값을, Cookie에서 token 값을, request body로 주어진 정보를 바탕으로 생성된 CommentRequestDto 객체를 가져온다.
  • 가져온 정보들을 CommentService의 register 메서드의 매개변수로 넣어주고, 이 메서드를 호출해 반환받은 값을 response body에 담아 201 코드와 함께 전달한다.

 

CommentService

@Transactional
public Long registerComment(final Long boardId, final Long userId, final CommentRequestDto params) {

    Board board = boardRepository.findById(boardId)
            .orElseThrow(() -> new CustomException(ErrorCode.POSTS_NOT_FOUND));
    User user = userRepository.findById(userId)
            .orElseThrow(() -> new CustomException(ErrorCode.USERS_NOT_FOUND));
    Comment entity = params.toEntity(board, user);
    commentRepository.save(entity);

    return entity.getId();
}
  • 매개변수로 게시글 id, 사용자 id, CommentRequestDto 객체를 받아온다.
  • BoardRepository의 findById 메서드와 UserRepository의 findById 메서드를 사용해 게시글 정보와 사용자 정보를 가져온다.
    • findById 메서드는 JPARepository에서 기본으로 제공하는 메서드이다.
  • 게시글 정보와 사용자 정보 그리고 CommentRequestDto 객체 정보를 통해 Comment 객체를 생성한다.
  • 생성한 Comment 객체를 CommentRepository의 save 메서드의 매개변수로 전달해 댓글 정보를 저장하고, 생성한 댓글의 id를 반환한다.
    • save 메서드는 JPARepository에서 기본으로 제공하는 메서드이다.

 

3.2. 댓글 READ

CommentApiController

@GetMapping("/posts/{id}/comments")
public ResponseEntity<List<CommentResponseDto>> findCommentByBoardId(@PathVariable final Long id) {

    return ResponseEntity.ok(commentService.findCommentByBoardId(id));
}
  • GET "/posts/{id}/comments" 요청이 오면 게시글에 달린 댓글을 조회하는 로직이 실행된다.
  • URI에서 게시글 id를 가져와 CommentService의 findCommentByBoardId 메서드의 매개변수로 전달하고, 이 메서드를 호출해 반환받은 값을 response body에 담아 200 코드와 함께 전달한다.

 

CommentService

findCommentByBoardId

public List<CommentResponseDto> findCommentByBoardId(final Long boardId) {

    List<Comment> commentList = commentRepository.findCommentByBoardId(boardId);
    List<CommentResponseDto> result = new ArrayList<>();
    for(Comment comment : commentList) {
        List<RecommentResponseDto> recommentList = toReCommentResponseDto(commentRepository.findRecommentByCommentId(comment.getId()));
        result.add(toCommentResponseDto(comment, recommentList));
    }
    return result;
}
  • 매개변수로 전달받은 게시글 id를 CommentRepository의 findCommentByBoardId 메서드의 매개변수로 전달하고, 이 메서드를 호출해 게시글에 달린 댓글 정보를 받아와 commentList에 저장한다.
  • commentList에 저장된 모든 댓글에 대한 대댓글 정보를 받아오고, CommentResponseDto 객체로 변환한 뒤 result 리스트에 추가한다.
    • 대댓글 정보를 받아오기 위해 CommentRepository의 findRecommentByCommentId 메서드의 매개변수로 댓글 id를 전달하고 반환받은 값을 toReCommentResponseDto 메서드에 전달해 Recomment 객체를 RecommentResponseDto 객체로 변환한다.

toCommnetResponseDto

private CommentResponseDto toCommentResponseDto(Comment comment, List<RecommentResponseDto> recommentList) {

    return CommentResponseDto.builder().entity(comment).user(comment.getUser())
            .recomment(recommentList).recommentSize(Long.valueOf(recommentList.size()))
            .build();
}
  • 매개변수로 Comment 객체와 RecommentResponseDto 객체 리스트를 받아온다.
  • 받아온 정보를 사용해 CommentResponseDto 객체를 생성하고, 반환한다.

toReCommentResponseDto

private List<RecommentResponseDto> toReCommentResponseDto(List<Comment> recommentList) {

    return recommentList.stream().map(comment -> new RecommentResponseDto(comment, comment.getUser())).collect(Collectors.toList());
}
  • 매개변수로 Comment 객체 리스트를 받아온다.
  • 받아온 리스트 안에 있는 모든 Comment 객체에 대해, Comment 객체 정보를 사용해 RecommentResponseDto 객체를 생성하고, 반환한다.

 

CommentRepository

findCommentByBoardId

@Override
public List<Comment> findCommentByBoardId(Long boardId) {

    return jpaQueryFactory.selectFrom(comment)
            .where(comment.board.id.eq(boardId), comment.parentId.isNull())
            .orderBy(comment.id.desc())
            .fetch();
}
  • comment 테이블에서 board.id가 매개변수로 받아온 boardId와 일치하고, parent_id의 값이 null인 데이터를 가져와 id 순으로 내림차순 정렬한 뒤 반환한다.

findReCommentByCommentId

@Override
public List<Comment> findRecommentByCommentId(Long commentId) {

    return jpaQueryFactory.selectFrom(comment)
            .where(comment.parentId.eq(commentId))
            .orderBy(comment.id.desc())
            .fetch();
}
  • comment 테이블에서 parent_id 값이 매개변수로 받아온 commentId와 일치하는 데이터를 가져와 id 순으로 내림차순 정렬한 뒤 반환한다.

 

3.3 댓글 UPDATE

CommentApiController

@PatchMapping("/comments")
public ResponseEntity<Long> updateCommentByCommentId(@CookieValue final String token, @RequestBody final UpdateCommentRequestDto params) {

    return ResponseEntity.ok(commentService.updateCommentByCommentId(params.getCommentId(), jwtTokenProvider.getUserId(token), params.getContent()));
}
  • PATCH "/comments" 요청이 들어오면 댓글을 수정하는 로직이 실행된다.
  • 매개변수로 Cookie에서 token 값과 request body 정보를 포함한 UpdateCommentRequestDto 객체를 받아온다.
  • token 값에서 추출한 userI와 매개변수로 받아온 UpdateCommentReqeustDto 객체의 값을 CommentService의 updateCommentByCommentId 메서드의 매개변수로 전달하고, 이 메서드를 호출해 반환받은 값을 response body에 담아 200 코드와 함께 반환한다.

 

CommentService

@Transactional
public Long updateCommentByCommentId(final Long commentId, final Long userId, final String content) {

    User user = userRepository.findById(userId).orElseThrow(() -> new CustomException(ErrorCode.USERS_NOT_FOUND));
    Comment comment = commentRepository.findById(commentId).orElseThrow(() -> new CustomException(ErrorCode.COMMENTS_NOT_FOUND));

    if(comment.getUser().getNickname().equals(user.getNickname())) {
        return commentRepository.updateCommentByCommentId(commentId, content);
    } else {
        throw new CustomException(ErrorCode.FORBIDDEN);
    }
}
  • 매개변수로 댓글 id, 사용자 id, 수정할 댓글 내용을 받아온다.
  • 매개변수로 받아온 사용자 id를 UserRepository의 findById 메서드의 매개변수로 전달해 사용자 정보를 받아와 user 변수에 저장하고, 댓글 id를 CommentRepository의 findById 메서드의 매개변수로 전달해 댓글 정보를 받아와 comment 변수에 저장한다.
    • findById 메서드는 JPARepository에서 기본으로 제공하는 메서드이다.
  • comment 정보 중 사용자 nickname 정보와 user의 nickname 정보를 비교해 일치하면, CommentRepository의 updateCommentByCommentId 메서드의 매개변수로 댓글 Id와 수정할 댓글 내용을 전달한다.
  • comment 정보 중 사용자 nickname 정보와 user의 nickname 정보가 일치하지 않는 경우 예외가 발생한다.

 

CommentRepository

@Override
public Long updateCommentByCommentId(Long commentId, String content) {

    jpaQueryFactory.update(comment)
            .set(comment.content, content)
            .where(comment.id.eq(commentId))
            .execute();

    return jpaQueryFactory.select(comment.id).from(comment)
            .where(comment.id.eq(commentId))
            .fetchOne();
}
  • 매개변수로 수정할 댓글 id와 댓글 내용을 받아온다.
  • comment 테이블에서 매개변수로 받아온 댓글 id와 일치하는 댓글의 내용을 수정하고, 수정한 댓글의 id를 반환한다.

 

3.4. 댓글 DELETE

CommentApiController

@DeleteMapping("/comments")
public ResponseEntity deleteCommentByCommentId(@CookieValue final String token, @RequestParam final Long commentId) {

    commentService.deleteCommentByCommentId(commentId, jwtTokenProvider.getUserId(token));
    return ResponseEntity.ok().build();
}
  • DELETE "/comments" 요청이 들어오면 댓글을 삭제하는 로직이 실행된다.
    • 댓글 삭제 로직은 실제로 DB에서 댓글이 삭제되는 것이 아닌 deleteYn의 값을 1로 변경하고, 내용을 "삭제된 댓글입니다."로 수정한다. 
  • Cookie에서 token 값을 가져오고, 쿼리 파라미터로 댓글 id를 가져온다.
  • CommentService의 deleteCommentByCommentId 메서드의 매개변수로 댓글 id와 token에서 추출한 사용자 id를 전달한다.
  • 댓글 삭제에 성공하면 200 코드를 반환한다.

 

CommentService

@Transactional
public void deleteCommentByCommentId(final Long commentId, final Long userId) {

    User user = userRepository.findById(userId).orElseThrow(() -> new CustomException(ErrorCode.USERS_NOT_FOUND));
    Comment comment = commentRepository.findById(commentId).orElseThrow(() -> new CustomException(ErrorCode.COMMENTS_NOT_FOUND));

    if(comment.getUser().getNickname().equals(user.getNickname())) {

        if(comment.getDeleteYn() == '1') {
            throw new CustomException(ErrorCode.ALREADY_DELETE);
        }
        commentRepository.deleteCommentByCommentId(commentId);
    }
    else {
        throw new CustomException(ErrorCode.FORBIDDEN);
    }
}
  • 매개변수로 댓글 id와 사용자 id를 받아온다.
  • 매개변수로 받아온 사용자 id를 UserRepository의 findById 메서드의 매개변수로 전달해 사용자 정보를 받아와 user 변수에 저장하고, 댓글 id를 CommentRepository의 findById 메서드의 매개변수로 전달해 댓글 정보를 받아와 comment 변수에 저장한다.
  • comment 정보 중 사용자 nickname 정보와 user의 nickname 정보를 비교해 일치하면
    • comment의 deleteYn 값을 확인해 1이면 예외가 발생하고, 1이 아닌 경우 CommentRepository의 deleteCommentByCommentId 메서드의 매개변수로 댓글 Id와 삭제할 댓글 내용을 전달한다.
  • comment 정보 중 사용자 nickname 정보와 user의 nickname 정보가 일치하지 않는 경우 예외가 발생한다.

 

CommentRepository

@Override
public void deleteCommentByCommentId(Long commentId) {

    jpaQueryFactory.update(comment)
            .set(comment.content, "삭제된 댓글입니다.")
            .set(comment.deleteYn, '1')
            .where(comment.id.eq(commentId))
            .execute();
}

 

  • 매개변수로 댓글 id를 받아온다.
  • comment 테이블에서 받아온 댓글 id와 일치하는 댓글의 내용을 "삭제된 댓글입니다."로 수정하고, deleteYn 값을 '1'로 변경한다.

 

이번 글에서는 댓글 기능과 관련된 코드를 리뷰해보았다. 다음 글에서는 알림 관련 코드를 리뷰해볼 것이다. 

728x90
LIST

댓글