본문 바로가기
Projects/청하-청년을 위한 커뮤니티 서비스

[청하] 15. 댓글 생성 기능 구현 및 게시글 상세조회 기능 수정

by Lpromotion 2024. 10. 10.

댓글 생성 API를 구현하고, 게시글 상세조회 기능을 수정한다.

 

1. 프로젝트 구조

src/main/java/com/example/withpeace/
│
├── domain/                 # 도메인 모델 (엔티티)
│   ├── Post.java           # 게시글 엔티티
│   └── Comment.java        # 댓글 엔티티
│
├── repository/                   # 데이터 접근 계층
│   ├── CommentRepository.java    # 댓글 레포지토리
│   └── PostRepository.java       # 게시글 레포지토리
│
├── dto/                                 # 데이터 전송 객체
│   └── response/
│       ├── CommentListResponseDto.java  # 댓글 리스트 응답 DTO
│       └── PostDetailResponseDto.java   # 게시글 상세조회 응답 DTO
│
├── controller/             # 컨트롤러 계층
│   └── PostController.java # 게시글 관련 API 엔드포인트
│
└── service/                # 비즈니스 로직 계층
    └── PostService.java    # 게시글 관련 비즈니스 로직

 

2. 도메인 엔티티 구현

Comment

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicUpdate
@Table(name = "comments")
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false, updatable = false, unique = true)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id", nullable = false)
    private Post post;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "writer_id", nullable = false)
    private User writer;

    @Column(name = "content", nullable = false)
    private String content;

    @Column(name = "create_date", nullable = false)
    private LocalDateTime createDate;

    @Builder
    public Comment(Post post, User writer, String content) {
        this.post = post;
        this.writer = writer;
        this.content = content;
        this.createDate = LocalDateTime.now();
    }

}

Comment 엔티티는 연관된 게시글, 작성자, 내용, 작성 시간을 포함한다.

  • `@ManyToOne`: 다대일 관계를 나타낸다. 여러 댓글이 하나의 게시글에 속할 수 있음을 의미한다.
    • `fetch = FetchType.LAZY`: 지연 로딩(필요한 시점에 연관된 데이터를 불러옴)으로 성능을 최적화한다.
  • `@JoinColumn`: 외래키를 지정한다. post_id와 writer_id가 각각 Post와 User 엔티티를 참조하는 외래키가 된다.

 

3. 레포지토리 구현

CommentRepository

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {

    @Query("SELECT c FROM Comment c WHERE c.post = :post")
    List<Comment> findCommentsByPost(Post post);
}

`CommentRepository`는 `JpaRepository`를 상속받아 기본적인 CRUD 기능을 제공한다.

`findCommentsByPost` 는 특정 게시글에 연관된 모든 댓글을 조회한다.

 

4. DTO 구현

4.1. CommentListResponseDto

@Builder
public record CommentListResponseDto(
        Long commentId, // 댓글 ID
        Long userId, // 작성자 ID
        String nickname, // 작성자 닉네임
        String profileImageUrl, // 작성자 프로필 이미지 URL
        String content, // 댓글 내용
        String createDate // 작성 날짜
) {}

게시글 상세조회 시 반환할 댓글 목록이다.

 

4.2. PostDetailResponseDto

@Builder
public record PostDetailResponseDto(
        Long postId, // 게시글 ID
        Long userId, // 작성자 ID
        String nickname, // 작성자 닉네임
        String profileImageUrl, // 작성자 프로필 이미지 URL
        String title, // 게시글 제목
        String content,  // 게시글 내용
        ETopic type, // 게시글 타입
        String createDate, // 작성 날짜
        List<String> postImageUrls, // 게시글 이미지 URL 목록
        List<CommentListResponseDto> comments // 댓글 목록
) {}

게시글 상세조회 응답 DTO로 게시글의 정보와 댓글 목록(`List<CommentListResponseDto>`)을 포함한다.

 

5. 컨트롤러 구현

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/posts")
public class PostController {
    private final PostService postService;
    // ... (기존 코드 생략)

    // 댓글 생성
    @PostMapping("/{postId}/comments/register")
    public ResponseDto<?> registerComment(@UserId Long userId, // 사용자 ID
                                          @PathVariable Long postId, // 게시글 ID
                                          @RequestBody String content // 댓글 내용
                                          ) {
        return ResponseDto.ok(postService.registerComment(userId, postId, content));
    }
}

사용자 ID, 게시글 ID, 댓글 내용을 받아 서비스로 전달한다.

  • `@PathVariable Long postId`: URL 경로에서 게시글 ID를 추출한다.
  • `@RequestBody String content`: 요청 본문에서 댓글 내용을 추출한다.

 

6. 서비스 구현

6.1. 사용자 및 게시글 조회 메서드

private User getUserById(Long userId) {
    // 사용자 ID로 사용자를 조회하고, 없으면 예외 발
    return userRepository.findById(userId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_USER));
}

private Post getPostById(Long postId) {
    // 게시글 ID로 게시글을 조회하고, 없으면 예외 발생
    return postRepository.findById(postId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_POST));
}

 

6.2. registerComment

@Transactional
public Boolean registerComment(Long userId, Long postId, String content) {
    // 사용자와 게시글 조회
    User user = getUserById(userId);
    Post post = getPostById(postId);

    // 댓글 생성 및 저장
    commentRepository.save(Comment.builder()
            .post(post)
            .writer(user)
            .content(content)
            .build());

    return true;
}

`registerComment` 메서드는 댓글 정보를 받아 댓글을 생성하는 로직을 수행한다.

  1. 사용자와 게시글을 조회한다. (존재 여부 확인)
  2. `commentRepository`의 `save` 메서드를 사용하여 주어진 정보로 생성한 댓글을 저장한다.

 

6.3. getPostDetail 수정

@Transactional
public PostDetailResponseDto getPostDetail(Long userId, Long postId) {
    // 사용자와 게시글 조회
    User user = getUserById(userId);
    Post post = getPostById(postId);

    // 게시글 이미지 URL 목록 조회
    List<String> postImageUrls = Optional.ofNullable(imageRepository.findUrlsByPost(post))
            .orElse(Collections.emptyList());
    
    // 댓글 목록 조회 및 DTO 변환
    List<CommentListResponseDto> comments = Optional.ofNullable(commentRepository.findCommentsByPost(post))
            .orElse(Collections.emptyList())
            .stream()
            .map(comment -> CommentListResponseDto.builder()
                    .commentId(comment.getId())
                    .userId(comment.getWriter().getId())
                    .nickname(comment.getWriter().getNickname())
                    .profileImageUrl(comment.getWriter().getProfileImage())
                    .content(comment.getContent())
                    .createDate(TimeFormatter.timeFormat(comment.getCreateDate()))
                    .build())
            .collect(Collectors.toList());

    // PostDetailResponseDto 생성 및 반환
    PostDetailResponseDto postDetailResponseDto =
            PostDetailResponseDto.builder()
                    .postId(postId)
                    .userId(post.getWriter().getId())
                    .nickname(user.getNickname())
                    .profileImageUrl(user.getProfileImage())
                    .title(post.getTitle())
                    .content(post.getContent())
                    .type(post.getType())
                    .createDate(TimeFormatter.timeFormat(post.getCreateDate()))
                    .postImageUrls(postImageUrls)
                    .comments(comments) // 댓글 목록 추가
                    .build();

    return postDetailResponseDto;
}

기존 `getPostDetail` 메서드를 수정하여 댓글 목록을 포함하도록 했다.

`commentRepository.findCommentsByPost(post)`로 게시글과 연관된 댓글들을 조회하고, 각 댓글을 `CommentListResponseDto`로 변환한 후 `PostDetailResponseDto` 에 포함시킨다.

 

7. API 응답 예시

요청 URL

[POST] http://cheongha.site/api/v1/posts/437/comments/register

 

Response Body

{
  "data": true,
  "error": null
}
반응형

댓글