Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
Tags
- 알고리즘
- Pager
- oh-my-zsh
- 맥
- MySQL
- mysql 표
- VI
- Less
- CS스터디
- 이진탐색
- spring boot
- 순차탐색
- 분할정복 방법
- mycli
- table status
- mysql 표 출력
- 데이크스트라
- 동적 프로그래밍 방법
- zsh theme
- 네트워킹데이
- 인프런워밍업클럽
- 스터디2기
- 오블완
- 욕심쟁이 방법
- cs
- zsh
- 오일러 경로
- 터미널
- 티스토리챌린지
- 인프런
Archives
- Today
- Total
Develop
[Spring Boot] 게시판 - 03. 댓글(Comment) API 구현 (대댓글, 소프트 삭제) 본문
안녕하세요 .ᐟ
게시판 프로젝트 3일차 입니다. (작심삼일도 여러번하면 결국엔 꾸준히 할 수 있습니다 화이팅)
오늘도 게시판의 핵심기능인 댓글과 대댓글을 구현했습니다.
오늘의 학습 목표
- Comment Entity 설계
- 댓글 CRUD API 구현
- 대댓글 계층 구조 구현
Entity 설계
연관관계 매핑
Comment는 세 가지 연관관계를 가집니다.
- Comment → Post: @ManyToOne으로 게시글과 연결
- Comment → User: @ManyToOne으로 작성자와 연결
- Comment → Comment: Self-Referencing으로 부모-자식 관계 구현
Self-Referencing 구조
대댓글 구조를 위해 같은 테이블의 다른 레코드를 참조합니다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Comment parent; // 부모 댓글
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List children = new ArrayList<>(); // 자식 댓글들
이 구조를 통해 다음과 같은 계층을 표현 할 수 있습니다.
- 무한 depth 의 댓글 구조를 단일 테이블로 구현 가능
- 부모-자식 관계를 명확하게 표현
- 대댓글 조회 시 parent.children 으로 간단히 접근 가능
댓글 (parent_id = null)
├─ 대댓글1 (parent_id = 1)
├─ 대댓글2 (parent_id = 1)
└─ 대댓글3 (parent_id = 1)
소프트 삭제
- deleted 플래그를 사용해 실제 데이터를 삭제하지 않고 삭제 표시만 했습니다.
- 대댓글이 있는 댓글의 맥락을 유지하기 위함입니다.
- 소프트 삭제를 선택한 이유
- 대댓글 맥락 유지
- 데이터 복구
- 히스토리 추적
- 실제 삭제 vs 소프트 삭제 판단 기준
- 대댓글이 있는 최상위 댓글 -> 소프트 삭제
- 대댓글이거나 자식이 없는 댓글 -> 실제 삭제
Repository
- Spring Data JPA 메서드 네이밍 규칙
- @Query 사용
Service 로직
댓글 작성
- parentId가 있으면 대댓글로 처리했습니다.
- Builder 패턴으로 Comment 객체를 생성하고 저장했습니다.
댓글 조회
- 최상위 댓글만 조회하거나, 모든 댓글을 조회하는 두 가지 메서드를 구현했습니다.
- filter(Comment::isParentComment)로 최상위 댓글을 필터링했습니다.
- Entity를 DTO로 변환해 반환했습니다.
댓글 수정
- 작성자 권한을 검증했습니다
- 삭제된 댓글은 수정할 수 없도록 처리했습니다
- 변경 감지를 활용해 updateContent() 메서드만 호출했습니다.
댓글 삭제
대댓글의 맥락을 유지하기 위해 삭제를 분기처리 했습니다.
- 자식이 있는 최상위 댓글: 소프트 삭제 (deleted = true)
- 대댓글 또는 자식이 없는 댓글: 실제 삭제 (repository.delete())
Controller
- /posts/{postId}/comments 경로로 댓글이 게시글에 종속적임을 URL로 표현했습니다.
DTO
- Entity 를 직접 받지 않고 ID 만 받음 (순환 참조 방지)
- paerintId는 선택적으로 받음 (일반 댓글일때는 null)
- 삭제된 댓글은 "삭제된 댓글입니다"를 표시
- 최상위 댓글일때만 children 포함
느낀 점
- 계층 구조 데이터 설계
- Self-Referencing으로 무한 depth를 구현할 수 있다는게 신기했습니다.
- 같은 테이블을 참조하는 방식이 어색하긴하지만 카테고리나 조직도에서도 활용할 수 있다고 하니 익숙해져야겠습니다.
- @Builder.Default 사용법
- 빌더 패턴 사용 시 List 나 boolean 같은 필드가 초기화 되지않는 문제가 간혹 발생하는데, 이때 @Builder.Default를 붙이면 명시적으로 기본값을 설정 할 수 있다는걸 배웠습니다.
- 비즈니스 로직을 Entity에 두는 이유
- 처음에는 Service 에서 로직을 처리하려고 했는데, 어제 Post Entity 에서 로직을 한번 짜본 뒤로 가독성이 좋다고 생각이들어서 이번에도 Entity에서 일부 로직을 작성했습니다.
- Service에서 Entity의 내부 구조가 바뀌어도 Service 는 수정할 필요가 없어서 좋았습니다.

'백엔드 > 게시판 만들기' 카테고리의 다른 글
| [Spring Boot] 게시판 - 06. 단위 테스트 작성하기 (Mockito & AssertJ) (0) | 2025.11.24 |
|---|---|
| [Spring Boot] 게시판 - 05. Validation 검증 (입력값 유효성 검사) (1) | 2025.11.23 |
| [Spring Boot] 게시판 - 04. 전역 예외 처리 (Global Exception Handler) (0) | 2025.11.22 |
| [Spring Boot] 게시판 - 02. Post API 개발 (페이징 처리, IntelliJ HTTP Client) (0) | 2025.11.20 |
| [Spring Boot] 게시판 - 01. 프로젝트 환경설정 , Entity 설계, 회원 관리 API 개발 (0) | 2025.11.19 |