-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 댓글 조회 api 개발 #165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feat] 댓글 조회 api 개발 #165
Changes from all commits
8c2e44d
31c0c5e
f20c5b4
04c0dbc
28c3b8b
33732a8
a7e1f86
5d8bdab
5be7ef0
ca6c4bb
58189b9
0c211cb
8bec836
db77f03
3b3805d
8668ec7
97b55a1
fe77395
8f4651d
c4f90f8
a4ec647
f0b3057
720b0f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,41 @@ | ||
| package konkuk.thip.comment.adapter.in.web; | ||
|
|
||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import konkuk.thip.comment.adapter.in.web.response.CommentForSinglePostResponse; | ||
| import konkuk.thip.comment.application.port.in.CommentShowAllUseCase; | ||
| import konkuk.thip.comment.application.port.in.dto.CommentShowAllQuery; | ||
| import konkuk.thip.common.dto.BaseResponse; | ||
| import konkuk.thip.common.security.annotation.UserId; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @Tag(name = "Comment Query API", description = "댓글 조회 관련 API") | ||
| @RestController | ||
| @RequiredArgsConstructor | ||
| public class CommentQueryController { | ||
|
|
||
| public final CommentShowAllUseCase commentShowAllUseCase; | ||
|
|
||
| @Operation( | ||
| summary = "댓글 전체 조회", | ||
| description = "특정 게시글(= 피드, 기록, 투표) 의 댓글과 대댓글들을 전체 조회합니다." | ||
| ) | ||
| @GetMapping("/comments/{postId}") | ||
| public BaseResponse<CommentForSinglePostResponse> showAllCommentsOfPost( | ||
| @Parameter(hidden = true) @UserId final Long userId, | ||
| @Parameter(description = "댓글을 조회할 게시글(= FEED, RECORD, VOTE)의 id값") | ||
| @PathVariable("postId") final Long postId, | ||
| @Parameter(description = "게시물 타입 (RECORD, VOTE, FEED)", example = "RECORD") | ||
| @RequestParam(value = "postType") final String postType, | ||
| @Parameter(description = "커서 (첫번째 요청시 : null, 다음 요청시 : 이전 요청에서 반환받은 nextCursor 값)") | ||
| @RequestParam(value = "cursor", required = false) final String cursor) { | ||
| return BaseResponse.ok(commentShowAllUseCase.showAllCommentsOfPost( | ||
| CommentShowAllQuery.of(postId, userId, postType, cursor) | ||
| )); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package konkuk.thip.comment.adapter.in.web.response; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record CommentForSinglePostResponse( | ||
| List<RootCommentDto> commentList, | ||
| String nextCursor, | ||
| boolean isLast | ||
| ) { | ||
| public record RootCommentDto( | ||
| Long commentId, | ||
| Long creatorId, | ||
| String creatorProfileImageUrl, | ||
| String creatorNickname, | ||
| String alias, | ||
| String aliasColor, | ||
| String postDate, // 댓글 작성 시각 (~ 전 형식) | ||
| String content, | ||
| int likeCount, | ||
| boolean isLike, | ||
| boolean isDeleted, // 삭제된 댓글인지 아닌지 | ||
| List<ReplyDto> replyList | ||
| ) { | ||
| public record ReplyDto( | ||
| Long commentId, | ||
| String parentCommentCreatorNickname, | ||
| Long creatorId, | ||
| String creatorProfileImageUrl, | ||
| String creatorNickname, | ||
| String alias, | ||
| String aliasColor, | ||
| String postDate, // 댓글 작성 시각 (~ 전 형식) | ||
| String content, | ||
| int likeCount, | ||
| boolean isLike | ||
| ) {} | ||
|
|
||
| /** | ||
| * 삭제된 루트 댓글에 매핑되는 response dto | ||
| * isDelete 제외 나머지 데이터는 모두 쓰레기 값으로 | ||
| */ | ||
| public static RootCommentDto createDeletedRootCommentDto(List<ReplyDto> replyList) { | ||
| return new RootCommentDto( | ||
| null, | ||
| null, | ||
| null, | ||
| null, | ||
| null, | ||
| null, | ||
| null, | ||
| null, | ||
| 0, | ||
| false, | ||
| true, // true | ||
| replyList); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package konkuk.thip.comment.adapter.out.persistence.repository; | ||
|
|
||
| import konkuk.thip.comment.application.port.out.dto.CommentQueryDto; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| public interface CommentQueryRepository { | ||
|
|
||
| List<CommentQueryDto> findRootCommentsWithDeletedByCreatedAtDesc(Long postId, String postTypeStr, LocalDateTime lastCreatedAt, int size); | ||
|
|
||
| List<CommentQueryDto> findAllActiveChildCommentsByCreatedAtAsc(Long rootCommentId); | ||
|
|
||
| Map<Long, List<CommentQueryDto>> findAllActiveChildCommentsByCreatedAtAsc(Set<Long> rootCommentIds); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,189 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package konkuk.thip.comment.adapter.out.persistence.repository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.querydsl.core.types.dsl.BooleanExpression; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.querydsl.core.types.dsl.Expressions; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.querydsl.jpa.impl.JPAQueryFactory; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.comment.adapter.out.jpa.QCommentJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.comment.application.port.out.dto.CommentQueryDto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.comment.application.port.out.dto.QCommentQueryDto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.entity.StatusType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.user.adapter.out.jpa.QAliasJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.user.adapter.out.jpa.QUserJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Repository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDateTime; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.*; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Repository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class CommentQueryRepositoryImpl implements CommentQueryRepository { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final JPAQueryFactory queryFactory; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final QCommentJpaEntity comment = QCommentJpaEntity.commentJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final QUserJpaEntity commentCreator = QUserJpaEntity.userJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final QAliasJpaEntity aliasOfCommentCreator = QAliasJpaEntity.aliasJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final QCommentJpaEntity parentComment = new QCommentJpaEntity("parentComment"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final QUserJpaEntity parentCommentCreator = new QUserJpaEntity("parentCommentCreator"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public List<CommentQueryDto> findRootCommentsWithDeletedByCreatedAtDesc(Long postId, String postTypeStr, LocalDateTime lastCreatedAt, int size) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 최상위 댓글(size+1) 프로젝션 생성 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QCommentQueryDto proj = new QCommentQueryDto( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.commentId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| commentCreator.userId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aliasOfCommentCreator.imageUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| commentCreator.nickname, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aliasOfCommentCreator.value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aliasOfCommentCreator.color, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.createdAt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.content, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.likeCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.status.eq(StatusType.INACTIVE) // 루트 댓글이 삭제된 상태인지 아닌지 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // WHERE 절 분리 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BooleanExpression whereClause = comment.postJpaEntity.postId.eq(postId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and(comment.postJpaEntity.dtype.eq(postTypeStr)) // dType 필터링 추가 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어근데 저희 post는 dtype으로 문자열이고 댓글의 postType은 enum인데 이거 나중에 통일해야할것같네욥 수정하면서 PostType.from()으로 파라미터 검증도 추가하면 좋을것같습니다 ! 일단은 이렇게 해도 좋을것같네여
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파라미터 검증은 QueryDto 내부에서 수행하고 있긴 합니다! |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and(comment.parent.isNull()) // 게시글의 최상위 댓글 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and(lastCreatedAt != null // 최신순 정렬 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? comment.createdAt.lt(lastCreatedAt) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : Expressions.TRUE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 조회 및 반환 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return queryFactory | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select(proj) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from(comment) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(commentCreator.aliasForUserJpaEntity, aliasOfCommentCreator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .where(whereClause) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orderBy(comment.createdAt.desc()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .limit(size + 1) // size + 1 개 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .fetch(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 커서 정렬 컬럼이 하나뿐이라 페이지 중복/누락 위험이 있습니다
.where(whereClause)
-.orderBy(comment.createdAt.desc())
+.orderBy(comment.createdAt.desc(), comment.commentId.desc())커서 로직도 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public List<CommentQueryDto> findAllActiveChildCommentsByCreatedAtAsc(Long rootCommentId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<CommentQueryDto> allDescendants = new ArrayList<>(); // 결과 누적용 리스트 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1) 부모 ID 집합에 루트 댓글 ID 추가 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Set<Long> parentIds = new HashSet<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parentIds.add(rootCommentId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 2) 자손 댓글용 프로젝션 (부모 댓글 ID·작성자 닉네임 포함) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QCommentQueryDto childProj = new QCommentQueryDto( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.commentId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.parent.commentId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parentCommentCreator.nickname, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| commentCreator.userId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aliasOfCommentCreator.imageUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| commentCreator.nickname, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aliasOfCommentCreator.value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aliasOfCommentCreator.color, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.createdAt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.content, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.likeCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.status.eq(StatusType.INACTIVE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 3) 단계별 자식 댓글 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!parentIds.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<CommentQueryDto> children = queryFactory | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select(childProj) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from(comment) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(comment.parent, parentComment) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(parentComment.userJpaEntity, parentCommentCreator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(commentCreator.aliasForUserJpaEntity, aliasOfCommentCreator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .where( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.parent.commentId.in(parentIds), // parentIds 하위의 모든 자식 댓글 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.status.eq(StatusType.ACTIVE) // 자식 댓글은 ACTIVE인 것만 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .fetch(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (children.isEmpty()) break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 4) 누적 및 다음 단계 부모 ID 집합 갱신 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| allDescendants.addAll(children); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parentIds = children.stream() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(CommentQueryDto::commentId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .collect(Collectors.toSet()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 5) 전체 자손 댓글을 깊이와 상관없이 작성 순으로 재정렬 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| allDescendants.sort(Comparator.comparing(CommentQueryDto::createdAt)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return allDescendants; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+92
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 자식 댓글 BFS 반복 쿼리 — 스키마 여건에 따라 단일 쿼리로 최적화 가능 현재는 깊이마다 쿼리를 반복(BFS)합니다. 최대 깊이에 비례해 쿼리 수가 증가합니다. 스키마에 comment.rootCommentId(루트 참조)가 존재한다면, 다음과 같이 단일 쿼리로 대체해 성능을 크게 개선할 수 있습니다.
스키마에 해당 컬럼이 없다면 현재 방식이 합리적입니다. 대안으로는 DB가 지원한다면 재귀 CTE(WITH RECURSIVE) 사용이 있으나, JPA/QueryDSL 표준에서의 이식성은 떨어집니다. 필요 시 JPA NativeQuery로 한 번에 가져오는 구현도 제안 가능합니다. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Map<Long, List<CommentQueryDto>> findAllActiveChildCommentsByCreatedAtAsc(Set<Long> rootCommentIds) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1) 루트 ID별로 최상위 매핑 초기화 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<Long, Long> idToRoot = new HashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (Long rootId : rootCommentIds) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| idToRoot.put(rootId, rootId); // 초기화 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 2) 결과 맵 초기화 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<Long, List<CommentQueryDto>> resultMap = new HashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (Long rootId : rootCommentIds) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resultMap.put(rootId, new ArrayList<>()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 3) 단계별 조회용 parentIds 초기화 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Set<Long> parentIds = new HashSet<>(rootCommentIds); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 4) 자손 댓글용 프로젝션 정의 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QCommentQueryDto childProj = new QCommentQueryDto( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.commentId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.parent.commentId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parentCommentCreator.nickname, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| commentCreator.userId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aliasOfCommentCreator.imageUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| commentCreator.nickname, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aliasOfCommentCreator.value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aliasOfCommentCreator.color, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.createdAt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.content, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.likeCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.status.eq(StatusType.INACTIVE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 5) 루프를 돌며 모든 깊이의 자식 댓글 조회 및 매핑 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!parentIds.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<CommentQueryDto> children = queryFactory | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select(childProj) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from(comment) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(comment.parent, parentComment) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(parentComment.userJpaEntity, parentCommentCreator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .leftJoin(commentCreator.aliasForUserJpaEntity, aliasOfCommentCreator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .where( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.parent.commentId.in(parentIds), // parentIds 하위의 모든 자식 댓글 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| comment.status.eq(StatusType.ACTIVE) // 자식 댓글은 ACTIVE인 것만 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .fetch(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (children.isEmpty()) break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Set<Long> nextParentIds = new HashSet<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (CommentQueryDto child : children) { // 조회한 자식 댓글들에 대하여 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Long rootId = idToRoot.get(child.parentCommentId()); // 현재 자식댓글의 루트 댓글(부모 아님, 루트임) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resultMap.get(rootId).add(child); // 해당 루트 ID의 리스트에 자식 댓글 추가 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 현재 자식 댓글도 다음 단계의 parentIds로 사용하기 위해 매핑 저장 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| idToRoot.put(child.commentId(), rootId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nextParentIds.add(child.commentId()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parentIds = nextParentIds; // 한단계 아래 계층에서 활용할 부모 댓글들 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 6) 각 루트별 value 리스트를 작성시간순으로 정렬 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resultMap.values().forEach(list -> list.sort(Comparator.comparing(CommentQueryDto::createdAt))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return resultMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+121
to
+188
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 굉장히 복잡하네요.. 우선 쿼리 횟수는 줄었으니 현 상황에서는 최선인 것 같습니다! 추후에 카톡에서 이야기 한 것처럼 Comment의 parentComment 대신 rootComment의 fk를 갖도록 하여 부모-자식 관계 => 루트-하위 관계로 풀어나가면 Querydsl의 로직이 조금 간단해질 것 같습니다! 저희가 초기에 Comment를 트리 구조로 잡은 이유가 '언급'이라는 기능에 따른 요구사항 때문이였는데, 이것도 다시보니 Comment에 'mentionUser'라는 User 테이블의 fk를 하나 가지고 있으면 해결 될 것 같네요! 추후 리팩토링에서 고려해봅시다!!
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵넵 좋습니다! 현재 답글을 보여주는 로직이 자신의 부모 댓글 자체 보다는 부모 댓글을 작성한 작성자와 자식 댓글의 최상위 루트 댓글이 중요하니 엔티티 구조를 수정하는 것도 좋은 것 같습니다! |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위에말한것처럼 여기에 postType에 대한 조건도 추가되어야할것같습니다! comment.postType.eq(searchPostType) 이런식으루..