티스토리 뷰

게시글 CRUD까지 구현했으니, 이제 댓글 CRUD도 구현해보겠다. 사실 댓글 로직은 게시글 로직과 거의 똑같다.

 

Entity 설계

댓글 Create(생성), Read(읽기), Update(업데이트), Delete(삭제)를 위해 필요한 엔티티 Comment를 만들어주었다.

 

Comment entity

package com.example.MyFreshmanCommunity.entity;

import com.example.MyFreshmanCommunity.dto.ArticleDto;
import com.example.MyFreshmanCommunity.dto.CommentDto;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import java.time.LocalDateTime;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String content;

    @Column
    private LocalDateTime createDate;

    @Column
    private int likesCount;

    @OnDelete(action = OnDeleteAction.SET_NULL)
    @ManyToOne
    @JoinColumn(name="member_id")
    private Member member;

    @ManyToOne
    @JoinColumn(name="article_id")
    private Article article;

    public static Comment createComment(CommentDto commentDto, Member member, Article article) {
        return new Comment(
                null,
                commentDto.getContent(),
                LocalDateTime.now(),
                0,
                member,
                article
        );
    }

    public void patch(CommentDto commentDto) { //수정할 내용이 있는 경우에만 동작
        if(commentDto.getContent() != null)
            this.content = commentDto.getContent();
    }

}

 

이번에도 외래키 제약조건 SET_NULL을 이용해서 member가 삭제되면, 해당 member와 연결된 comment의 member 필드는 null이 되도록 설정해주었다.

 

게시글과 마찬가지로 createComment 메서드는 새로운 Comment객체를 생성하고 반환하는 역할을 하고, patch 메서드는 수정할 내용이 담긴 commentDto 객체를 받아 댓글 내용을 수정하는 역할을 한다. null이 아닐 경우에만(수정할 내용이 있는 경우에만) 동작하도록 했다.

 


DTO

 

CommentDto

package com.example.MyFreshmanCommunity.dto;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
//@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor
public class CommentDto {
    private String content;
}

 

댓글 생성 및 수정 시 클라이언트에서 서버로 데이터를 전송하는 데 사용되는 dto이다.

댓글 내용(content)을 담고있다.

 

CommentResponseDto

package com.example.MyFreshmanCommunity.dto;

import com.example.MyFreshmanCommunity.entity.Article;
import com.example.MyFreshmanCommunity.entity.Comment;
import com.example.MyFreshmanCommunity.entity.Major;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
//@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor
public class CommentResponseDto {
    private Long id;
    private String content;
    private LocalDateTime createDate;
    private int likesCount;
    private MemberInfoDto memberInfo;
    private LoginResponseDto member;

    public static CommentResponseDto createCommentDto(Comment comment){
        
        MemberInfoDto memberInfo = comment.getMember() != null ?
                MemberInfoDto.createMemberDto(comment.getMember()) : null;
        
        return new CommentResponseDto(
                comment.getId(),
                comment.getContent(),
                comment.getCreateDate(),
                comment.getLikesCount(),
                memberInfo,
                LoginResponseDto.createLoginDto(comment.getMember())
        );
    }
}

 

CommentResponseDto는 반대로 서버에서 클라이언트로 데이터를 전송하는 데 사용되는 dto이다. 

게시글과 마찬가지로 MemberInfoDto를 사용해서 사용자 정보 중 민감한 데이터를 제외하고 필요한 정보만을 전달하도록 구현했다.

 

(자세한 MemberInfoDto는 게시글 CRUD 참고)

 


Repository

 

CommentRepository

package com.example.MyFreshmanCommunity.repository;

import com.example.MyFreshmanCommunity.dto.CommentDto;
import com.example.MyFreshmanCommunity.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface CommentRepository extends JpaRepository<Comment, Long> {
    
    //좋아요가 10개 이상이면 베스트댓글
    @Query(value = "SELECT * FROM comment " +
            "WHERE article_id = :articleId " +
            "ORDER BY CASE WHEN likes_count >= 10 THEN 0 ELSE 1 END, " +
            "CASE WHEN likes_count >= 10 THEN likes_count ELSE NULL END DESC, " +
            "create_date ASC",
            nativeQuery = true)
    List<Comment> findByArticleId(Long articleId);
}

 

좋아요 수가 10개 이상인 댓글을 좋아요 수에 따라 내림차순으로 정렬하고, 같은 좋아요 수를 가진 댓글은 생성 날짜 순으로 오름차순 정렬하며, 좋아요 수가 10개 미만인 댓글은 생성 날짜 순으로 오름차순으로 정렬하는 쿼리를 작성해 주었다.

 


 

Service

 

CommentService

@Service
@Slf4j
@RequiredArgsConstructor
public class CommentService {

    private final CommentRepository commentRepository;
    private final ArticleRepository articleRepository;

    //댓글 조회
    public List<CommentResponseDto> showAll(Long articleId) {
        List<Comment> comments = commentRepository.findByArticleId(articleId);
        List<CommentResponseDto> dtos = new ArrayList<CommentResponseDto>();
        for (Comment c : comments) {
            CommentResponseDto dto = CommentResponseDto.createCommentDto(c);
            dtos.add(dto);
        }
        return dtos;
    }

 

댓글 조회 로직에서 위에서 만들었던 CommentResponseDto로 반환을 해주는 모습을 확인할 수 있다.

    //댓글 생성
    public CommentResponseDto create(Long articleId, CommentDto commentDto, HttpSession session) {
        Member member = (Member) session.getAttribute("member");
        Article article = articleRepository.findById(articleId).
                orElseThrow(() -> new NotFoundException("대상 게시글이 없습니다."));
        Comment comment = Comment.createComment(commentDto, member, article);
        Comment save = commentRepository.save(comment);
        return CommentResponseDto.createCommentDto(save);
    }
    //댓글 수정
    public CommentResponseDto update(Long commentId, CommentDto commentDto, HttpSession session) {
        Comment target = commentRepository.findById(commentId).
                orElseThrow(() -> new NotFoundException("대상 댓글이 없습니다."));
        Member member = (Member) session.getAttribute("member");

        if(!target.getMember().getId().equals(member.getId())) {
            throw new NotPermissionException("다른 사람의 댓글을 수정할 수 없습니다.");
        }

        target.patch(commentDto);

        Comment updated = commentRepository.save(target);
        return CommentResponseDto.createCommentDto(updated);
    }

 

생성, 수직 로직에선 위에서 만든 CommentDto를 사용하고 있음을 볼 수 있다.

    //댓글 삭제
    public void delete(Long commentId, HttpSession session) {
        Comment comment = commentRepository.findById(commentId).
                orElseThrow(() -> new NotFoundException("대상 댓글이 없습니다."));
        Member member = (Member) session.getAttribute("member");

        if(!comment.getMember().getId().equals(member.getId())) {
            throw new NotPermissionException("다른 사람의 댓글을 삭제할 수 없습니다.");
        }

        commentRepository.delete(comment);
    }

 

댓글도 게시글과 마찬가지로 지우려는 댓글이 로그인한 유저가 아닌 경우 수정, 삭제 불가하도록 해주기 위하여 member의 고유한 값이 id로 비교하여 처리해주었다. (객체끼리만 비교하면 속성 값이 같아도 서로 다른 메모리 주소에 위치해서 false를 반환하는 오류 발생)

 


Controller

 

CommentApiController

 

@RestController
@RequiredArgsConstructor
public class CommentApiController {

    private final CommentService commentService;

    //댓글 조회
    @GetMapping("/article/{articleId}/comment")
    public ResponseEntity<List<CommentResponseDto>> showAll(@PathVariable Long articleId){
        List<CommentResponseDto> comments = commentService.showAll(articleId);
        return ResponseEntity.status(HttpStatus.OK).body(comments);
    }

 

    //댓글 생성
    @PostMapping("/article/{articleId}/comment")
    public ResponseEntity<CommentResponseDto> create(@PathVariable Long articleId, @RequestBody CommentDto commentDto,
                                                     HttpServletRequest request) {
        HttpSession session = request.getSession();
        CommentResponseDto comment = commentService.create(articleId, commentDto, session);
        return ResponseEntity.status(HttpStatus.OK).body(comment);
    }

 

    //댓글 수정
    @PatchMapping("/comment/{commentId}")
    public ResponseEntity<CommentResponseDto> update(@PathVariable Long commentId, @RequestBody CommentDto commentDto,
                                                     HttpServletRequest request) {
        HttpSession session = request.getSession();
        CommentResponseDto comment = commentService.update(commentId, commentDto, session);
        return ResponseEntity.status(HttpStatus.OK).body(comment);
    }
    //댓글 삭제
    @DeleteMapping("/comment/{commentId}")
    public ResponseEntity<String> delete(@PathVariable Long commentId, HttpServletRequest request) {
        HttpSession session = request.getSession();
        commentService.delete(commentId, session);
        return ResponseEntity.status(HttpStatus.OK).body("댓글 삭제 완료");
    }

 

@PathVariable을 사용하여 URL 경로의 일부를 메서드의 파라미터로 바인딩해주었다. ex)commentId, articleId

 

댓글 생성, 수정, 삭제 로직에서는 현재 로그인되어있는 멤버의 정보가 필요하므로 HttpServletRequest request, HttpSession session = request.getSession(); 을 통해 세션에 있는 객체를 가져와 articleService에 인자로 전달해주었다.

 


Postman을 통한 데이터 확인

postman을 통해 데이터가 잘 넘어가는지 확인해주었다.

 

댓글 조회
댓글 생성
댓글 수정
댓글 수정 실패
댓글 삭제
댓글 삭제 실패

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/08   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함