11package com .back .domain .board .comment .repository .custom ;
22
33import com .back .domain .board .comment .dto .CommentListResponse ;
4+ import com .back .domain .board .comment .dto .MyCommentResponse ;
45import com .back .domain .board .comment .dto .QCommentListResponse ;
6+ import com .back .domain .board .comment .dto .QMyCommentResponse ;
57import com .back .domain .board .comment .entity .Comment ;
68import com .back .domain .board .comment .entity .QComment ;
79import com .back .domain .board .common .dto .QAuthorResponse ;
10+ import com .back .domain .board .post .entity .QPost ;
811import com .back .domain .user .entity .QUser ;
912import com .back .domain .user .entity .QUserProfile ;
13+ import com .querydsl .core .BooleanBuilder ;
1014import com .querydsl .core .types .Order ;
1115import com .querydsl .core .types .OrderSpecifier ;
1216import com .querydsl .core .types .dsl .BooleanExpression ;
@@ -30,23 +34,26 @@ public class CommentRepositoryImpl implements CommentRepositoryCustom {
3034 /**
3135 * 특정 게시글의 댓글 목록 조회
3236 * - 총 쿼리 수: 3회
33- * 1.부모 댓글 목록을 페이징/정렬 조건으로 조회
34- * 2.부모 ID 목록으로 자식 댓글 전체 조회
35- * 3.부모 총 건수( count) 조회
37+ * 1.부모 댓글 목록 조회 (User, UserProfile join)
38+ * 2. 자식 댓글 목록 조회 (User, UserProfile join)
39+ * 3.부모 전체 count 조회
3640 *
37- * @param postId 게시글 Id
41+ * @param postId 게시글 ID
3842 * @param pageable 페이징 + 정렬 조건
3943 */
4044 @ Override
41- public Page <CommentListResponse > getCommentsByPostId (Long postId , Pageable pageable ) {
45+ public Page <CommentListResponse > findCommentsByPostId (Long postId , Pageable pageable ) {
4246 QComment comment = QComment .comment ;
4347
44- // 1. 정렬 조건 생성
48+ // 1. 검색 조건 생성
49+ BooleanExpression condition = comment .post .id .eq (postId ).and (comment .parent .isNull ());
50+
51+ // 2. 정렬 조건 생성
4552 List <OrderSpecifier <?>> orders = buildOrderSpecifiers (pageable );
4653
47- // 2 . 부모 댓글 조회 (페이징 적용)
54+ // 3 . 부모 댓글 조회 (페이징 적용)
4855 List <CommentListResponse > parents = fetchComments (
49- comment . post . id . eq ( postId ). and ( comment . parent . isNull ()) ,
56+ condition ,
5057 orders ,
5158 pageable .getOffset (),
5259 pageable .getPageSize ()
@@ -57,42 +64,117 @@ public Page<CommentListResponse> getCommentsByPostId(Long postId, Pageable pagea
5764 return new PageImpl <>(parents , pageable , 0 );
5865 }
5966
60- // 3 . 부모 ID 수집
67+ // 4 . 부모 ID 수집
6168 List <Long > parentIds = parents .stream ()
6269 .map (CommentListResponse ::getCommentId )
6370 .toList ();
6471
65- // 4 . 자식 댓글 조회 (부모 집합에 대한 전체 조회)
72+ // 5 . 자식 댓글 조회 (부모 집합에 대한 전체 조회)
6673 List <CommentListResponse > children = fetchComments (
6774 comment .parent .id .in (parentIds ),
6875 List .of (comment .createdAt .asc ()), // 시간순 정렬
6976 null ,
7077 null
7178 );
7279
73- // 5 . 부모-자식 매핑
80+ // 6 . 부모-자식 매핑
7481 mapChildrenToParents (parents , children );
7582
76- // 6. 전체 부모 댓글 수 조회
77- Long total = queryFactory
78- .select (comment .count ())
83+ // 7. 전체 부모 댓글 수 조회
84+ long total = countComments (condition );
85+
86+ return new PageImpl <>(parents , pageable , total );
87+ }
88+
89+ /**
90+ * 특정 사용자의 댓글 목록 조회
91+ * - 총 쿼리 수: 2회
92+ * 1. 댓글 목록 조회 (Comment, Post join)
93+ * 2. 전체 count 조회
94+ *
95+ * @param userId 사용자 ID
96+ * @param pageable 페이징 + 정렬 조건
97+ */
98+ @ Override
99+ public Page <MyCommentResponse > findCommentsByUserId (Long userId , Pageable pageable ) {
100+ QComment comment = QComment .comment ;
101+ QComment parent = new QComment ("parent" );
102+ QPost post = QPost .post ;
103+
104+ // 1. 검색 조건 생성
105+ BooleanExpression condition = comment .user .id .eq (userId );
106+
107+ // 2. 정렬 조건 생성
108+ List <OrderSpecifier <?>> orders = buildOrderSpecifiers (pageable );
109+
110+ // 3. 댓글 목록 조회
111+ List <MyCommentResponse > comments = queryFactory
112+ .select (new QMyCommentResponse (
113+ comment .id ,
114+ post .id ,
115+ post .title ,
116+ parent .id ,
117+ parent .content .substring (0 , 50 ),
118+ comment .content ,
119+ comment .likeCount ,
120+ comment .createdAt ,
121+ comment .updatedAt
122+ ))
79123 .from (comment )
80- .where (comment .post .id .eq (postId ).and (comment .parent .isNull ()))
81- .fetchOne ();
124+ .leftJoin (comment .parent , parent )
125+ .leftJoin (comment .post , post )
126+ .where (condition )
127+ .orderBy (orders .toArray (new OrderSpecifier [0 ]))
128+ .offset (pageable .getOffset ())
129+ .limit (pageable .getPageSize ())
130+ .fetch ();
131+
132+ // 결과가 없으면 즉시 빈 페이지 반환
133+ if (comments .isEmpty ()) {
134+ return new PageImpl <>(comments , pageable , 0 );
135+ }
136+
137+ // 4. 전체 댓글 수 조회
138+ long total = countComments (condition );
82139
83- return new PageImpl <>(parents , pageable , total != null ? total : 0L );
140+ return new PageImpl <>(comments , pageable , total );
84141 }
85142
86143 // -------------------- 내부 메서드 --------------------
87144
145+ /**
146+ * 정렬 조건 생성
147+ * - Pageable의 Sort 정보를 QueryDSL OrderSpecifier 목록으로 변환
148+ */
149+ private List <OrderSpecifier <?>> buildOrderSpecifiers (Pageable pageable ) {
150+ QComment comment = QComment .comment ;
151+ PathBuilder <Comment > entityPath = new PathBuilder <>(Comment .class , comment .getMetadata ());
152+ List <OrderSpecifier <?>> orders = new ArrayList <>();
153+
154+ for (Sort .Order order : pageable .getSort ()) {
155+ String property = order .getProperty ();
156+
157+ // 화이트리스트에 포함된 필드만 허용
158+ if (!ALLOWED_SORT_FIELDS .contains (property )) {
159+ // 허용되지 않은 정렬 키는 무시 (런타임 예외 대신 안전하게 스킵)
160+ continue ;
161+ }
162+
163+ Order direction = order .isAscending () ? Order .ASC : Order .DESC ;
164+ orders .add (new OrderSpecifier <>(direction , entityPath .getComparable (property , Comparable .class )));
165+ }
166+
167+ // 명시된 정렬이 없으면 기본 정렬(createdAt DESC) 적용
168+ if (orders .isEmpty ()) {
169+ orders .add (new OrderSpecifier <>(Order .DESC , comment .createdAt ));
170+ }
171+
172+ return orders ;
173+ }
174+
88175 /**
89176 * 댓글 조회
90177 * - User / UserProfile join (N+1 방지)
91- *
92- * @param condition where 조건
93- * @param orders 정렬 조건
94- * @param offset 페이징 offset (null이면 미적용)
95- * @param limit 페이징 limit (null이면 미적용)
96178 */
97179 private List <CommentListResponse > fetchComments (
98180 BooleanExpression condition ,
@@ -147,32 +229,16 @@ private void mapChildrenToParents(List<CommentListResponse> parents, List<Commen
147229 }
148230
149231 /**
150- * 정렬 조건 생성
151- * - Pageable의 Sort 정보를 QueryDSL OrderSpecifier 목록으로 변환
232+ * 전체 댓글 개수 조회
233+ * - 단순 count 쿼리 1회
152234 */
153- private List < OrderSpecifier <?>> buildOrderSpecifiers ( Pageable pageable ) {
235+ private long countComments ( BooleanExpression condition ) {
154236 QComment comment = QComment .comment ;
155- PathBuilder <Comment > entityPath = new PathBuilder <>(Comment .class , comment .getMetadata ());
156- List <OrderSpecifier <?>> orders = new ArrayList <>();
157-
158- for (Sort .Order order : pageable .getSort ()) {
159- String property = order .getProperty ();
160-
161- // 화이트리스트에 포함된 필드만 허용
162- if (!ALLOWED_SORT_FIELDS .contains (property )) {
163- // 허용되지 않은 정렬 키는 무시 (런타임 예외 대신 안전하게 스킵)
164- continue ;
165- }
166-
167- Order direction = order .isAscending () ? Order .ASC : Order .DESC ;
168- orders .add (new OrderSpecifier <>(direction , entityPath .getComparable (property , Comparable .class )));
169- }
170-
171- // 명시된 정렬이 없으면 기본 정렬(createdAt DESC) 적용
172- if (orders .isEmpty ()) {
173- orders .add (new OrderSpecifier <>(Order .DESC , comment .createdAt ));
174- }
175-
176- return orders ;
237+ Long total = queryFactory
238+ .select (comment .count ())
239+ .from (comment )
240+ .where (condition )
241+ .fetchOne ();
242+ return total != null ? total : 0L ;
177243 }
178244}
0 commit comments