웹개발/SpringBoot

Springboot로 게시판(Post&Comment)만들기 - 두 테이블 연결

bergerac 2025. 6. 1. 09:11

https://github.com/seoyoonchoi/Korea_sleep_Tech_springBoot/tree/main/src/main/java/com/example/korea_sleepTech_springboot

 

Korea_sleep_Tech_springBoot/src/main/java/com/example/korea_sleepTech_springboot at main · seoyoonchoi/Korea_sleep_Tech_springB

Contribute to seoyoonchoi/Korea_sleep_Tech_springBoot development by creating an account on GitHub.

github.com

실제 코드를 더 확인하고 싶다면 위 링크를 통해서 확인할 수 있다.

 

📁 프로젝트 구성

com.example.korea_sleepTech_springboot
├── entity
│   ├── D_Post.java
│   └── D_Comment.java
├── dto
│   ├── request
│   │   ├── PostCreate/UpdateRequestDto.java
│   │   └── CommentCreate/UpdateRequestDto.java
│   └── response
│       ├── PostListResponseDto
│       ├── PostDetailResponseDto
│       ├── CommentResponseDto
│       └── PostWithCommentCountResponseDto
├── controller
│   ├── PostController
│   └── CommentController
├── service
│   ├── PostService / PostServiceImpl
│   └── CommentService / CommentServiceImpl
├── repository
│   ├── PostRepository
│   └── CommentRepository
└── common
    └── ResponseDto / ApiMappingPattern

Entity 설계 - 양방향 관계의 구현

D_post(게시글)

@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<D_Comment> comments = new ArrayList<>();

 

  • mappedBy = "post": D_Comment 엔티티의 post 필드와 연결
  • cascade = ALL: 게시글 삭제 시 댓글도 함께 삭제
  • orphanRemoval = true: Post에서 댓글 객체를 제거하면, DB에서도 삭제됨
  • fetch = LAZY: Post 조회 시 댓글을 즉시 불러오지 않고 필요 시 로딩

D_Comment (댓글)

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private D_Post post;

 

 

  • 여러 댓글이 하나의 게시글에 속하는 N:1 관계
  • 외래키 post_id로 연결됨
  • @ToString(exclude = "post"): 순환 참조 방지

연관관계 편의 메서드

public void addComment(D_Comment comment) {
    this.comments.add(comment);
    comment.setPost(this);
}

public void removeComment(D_Comment comment) {
    this.comments.remove(comment);
    comment.setPost(null);
}

 

 

  • PA의 연관관계 주인은 @ManyToOne이 붙은 D_Comment 쪽
  • 하지만 양쪽 모두 객체 그래프가 연결되어 있어야 영속성 컨텍스트가 동작함

DTO 설계

요청(Request)

  • PostCreateRequestDto, PostUpdateRequestDto
  • CommentCreateRequestDto, CommentUpdateRequestDto

> 모든 필드에 @NotBlank 또는 @Valid 지정 → 유효성 검증 적용
> Comment 작성 시 postId는 API URI에서 전달받음 (@PathVariable)

 

응답(Response)

  • PostDetailResponseDto: 댓글 포함 전체 게시글 응답
  • PostListResponseDto: 목록 조회 시 사용
  • CommentResponseDto: 댓글 단건 응답
  • PostWithCommentCountResponseDto: 댓글 수 통계 포함 게시글 응답

> CommentResponseDto는 Builder 패턴의 커스텀 생성 방식을 통해 유연성 확보

Controller 계층

PostController

  • /api/v1/posts
  • CRUD + 확장 기능 제공
// 댓글 포함 단건 조회
@GetMapping("/{id}")
public ResponseEntity<ResponseDto<PostDetailResponseDto>> getPostById(@PathVariable Long id)

 

// 댓글 수 기준 상위 5개 게시글
@GetMapping("/top-comments")
public ResponseEntity<ResponseDto<List<PostWithCommentCountResponseDto>>> getTop5PostsByComments()

📌 서비스 계층에서 List<Object[]>를 받아 직접 DTO 변환 → 유연한 통계 처리

 

CommentController

  • /api/v1/comments
  • 댓글 CUD 전용
  • postId는 항상 URI로부터 전달
@PostMapping
public ResponseEntity<ResponseDto<CommentResponseDto>> createComment(@PathVariable Long postId, @RequestBody dto)

 

Service 계층

게시글 CRUD 핵심 흐름

  • 생성(create): PostCreateRequestDto → D_Post → 저장 → DTO 변환
  • 수정(update): 엔티티 조회 후 필드 변경 → save()
  • 삭제(delete): 댓글 연관관계 먼저 해제 → deleteById()

댓글 처리 핵심 흐름

  • 댓글 생성 시 post.addComment(comment) 호출
  • 수정/삭제 시 postId와 commentId의 일치 여부를 검증
  • 삭제 전 반드시 post.removeComment(comment) 호출 → orphanRemoval 발동

댓글 수 기반 통계 기능

List<Object[]> results = postRepository.findTop5ByOrderByCommentsSizeDesc();

 

  • JPQL 또는 native SQL 결과가 Object[]로 반환됨
  • 각 row를 PostWithCommentCountResponseDto로 변환
  • ((Number) row[4]).intValue() 등 명시적 캐스팅 필수