-
Notifications
You must be signed in to change notification settings - Fork 0
✨Feat: 인기 전문가 칼럼 조회 Redis 캐시 도입 및 DTO 조회 구조 개선 #23
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
Changes from all commits
d6826a0
0938457
c37359a
225eaad
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 |
|---|---|---|
|
|
@@ -10,4 +10,4 @@ public record HomePostRow( | |
| LocalDateTime createdAt, | ||
| Long likeCount, | ||
| Long commentCount | ||
| ) {} | ||
| ) {} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.backend.farmon.dto.home; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| @NoArgsConstructor | ||
| public class PopularExpertPostRow { | ||
| private Long postId; | ||
| private String title; | ||
| private String content; | ||
| private String writer; | ||
| private String profileImageUrl; | ||
| private String firstImageStoredFileName; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,10 +4,12 @@ | |
| import com.backend.farmon.apiPayload.exception.GeneralException; | ||
| import com.backend.farmon.domain.*; | ||
| import com.backend.farmon.dto.home.HomePostRow; | ||
| import com.backend.farmon.dto.home.PopularExpertPostRow; | ||
| import com.backend.farmon.dto.post.PostType; | ||
| import com.backend.farmon.repository.BoardRepository.BoardRepository; | ||
| import com.querydsl.core.BooleanBuilder; | ||
| import com.querydsl.core.types.Projections; | ||
| import com.querydsl.core.types.dsl.CaseBuilder; | ||
| import com.querydsl.core.types.dsl.Expressions; | ||
| import com.querydsl.jpa.JPAExpressions; | ||
| import com.querydsl.jpa.impl.JPAQueryFactory; | ||
|
|
@@ -20,8 +22,6 @@ | |
|
|
||
| import java.util.*; | ||
|
|
||
| import static com.backend.farmon.domain.QPostImg.postImg; | ||
|
|
||
| @Slf4j | ||
| @Repository | ||
| @RequiredArgsConstructor | ||
|
|
@@ -33,8 +33,10 @@ public class PostRepositoryImpl implements PostRepositoryCustom { | |
| private final QBoard board = QBoard.board; | ||
| private final QPost originalPost = new QPost("originalPost"); | ||
| private final QComment comment = QComment.comment; | ||
|
|
||
| private final QCrop crop = QCrop.crop; | ||
| private final QUser user = QUser.user; | ||
| private final QExpert expert = QExpert.expert; | ||
| private final QPostImg postImg = QPostImg.postImg; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * 공통 select (원본 게시글 기준) + 좋아요/댓글 count를 한 번에 가져오기 위한 프로젝션 | ||
|
|
@@ -142,33 +144,72 @@ public List<HomePostRow> findTopPostsByPostTypeWithCounts(PostType postType, int | |
|
|
||
| // 인기 전문가 칼럼 6개 조회 | ||
| @Override | ||
| public List<Post> findTop6ExpertColumnPostsByPostId(List<Long> popularPostsIdList) { | ||
| return queryFactory.select(post) | ||
| public List<PopularExpertPostRow> findTopExpertColumnRowsByPopularIds(List<Long> popularPostsIdList, int limit) { | ||
| QPostImg pi2 = new QPostImg("pi2"); | ||
|
|
||
| boolean hasPinned = popularPostsIdList != null && !popularPostsIdList.isEmpty(); | ||
|
|
||
| var query = queryFactory | ||
| .select(Projections.constructor( | ||
| PopularExpertPostRow.class, | ||
| post.id, | ||
| post.postTitle, | ||
| post.postContent, | ||
| user.userName, | ||
| expert.profileImageUrl, | ||
| postImg.storedFileName | ||
| )) | ||
| .from(post) | ||
| .join(post.board, board).fetchJoin() | ||
| .leftJoin(post.postlikes, likeCount) | ||
| .where( | ||
| board.postType.eq(PostType.EXPERT_COLUMN) // 전문가 칼럼 조건 | ||
| .and( | ||
| popularPostsIdList != null && !popularPostsIdList.isEmpty() | ||
| ? post.id.in(popularPostsIdList).or(post.id.notIn(popularPostsIdList)) | ||
| : null | ||
| ) | ||
| ) | ||
| .groupBy(post) | ||
| .orderBy( | ||
| // 인기 게시글 우선 정렬 | ||
| popularPostsIdList != null && !popularPostsIdList.isEmpty() | ||
| ? Expressions.stringTemplate("CASE WHEN {0} IN ({1}) THEN 1 ELSE 2 END", post.id, Expressions.constant(popularPostsIdList)).asc() | ||
| : null, | ||
| // popularPostsIdList 내부 정렬 | ||
| popularPostsIdList != null && !popularPostsIdList.isEmpty() | ||
| ? Expressions.stringTemplate("FIELD({0}, {1})", post.id, Expressions.constant(popularPostsIdList)).asc() | ||
| : null, | ||
| likeCount.count().desc(), // 좋아요 개수 내림차순 | ||
| post.createdAt.desc() // 작성일 내림차순 | ||
| ) | ||
| .limit(6) // 6개 제한 | ||
| .join(post.board, board) | ||
| .join(post.user, user) | ||
| .leftJoin(user.expert, expert) | ||
| .leftJoin(likeCount).on(likeCount.post.id.eq(post.id)) | ||
| // 첫 이미지 1개만 LEFT JOIN | ||
| .leftJoin(postImg).on(postImg.id.eq( | ||
| JPAExpressions.select(pi2.id.min()) | ||
| .from(pi2) | ||
| .where(pi2.post.id.eq(post.id)) | ||
| )) | ||
| .where(board.postType.eq(PostType.EXPERT_COLUMN)) | ||
| .groupBy( | ||
| post.id, | ||
| post.postTitle, | ||
| post.postContent, | ||
| user.userName, | ||
| expert.profileImageUrl, | ||
| postImg.storedFileName | ||
| ); | ||
|
|
||
| // QueryDSL orderBy에 null 전달 방지 + pinned 조건부 생성 | ||
| if (hasPinned) { | ||
| // pinned 우선 정렬(1) / 나머지(2) | ||
| var pinnedFirstOrderExpr = new CaseBuilder() | ||
| .when(post.id.in(popularPostsIdList)).then(1) | ||
| .otherwise(2); | ||
|
|
||
| // pinned 내부 순서 유지 (MySQL: FIELD) | ||
| var pinnedInnerOrderExpr = Expressions.numberTemplate( | ||
| Integer.class, | ||
| "FIELD({0}, {1})", | ||
| post.id, | ||
| Expressions.constant(popularPostsIdList) | ||
| ); | ||
|
Comment on lines
+191
to
+196
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check for database configurations across the project
echo "=== Database Configuration Files ==="
fd -e properties -e yaml | head -20
echo -e "\n=== Searching for datasource and database configs ==="
rg "spring.datasource|spring.jpa|h2|mysql" -t yaml -t properties -A 2
echo -e "\n=== Checking pom.xml for database dependencies ==="
if [ -f "pom.xml" ]; then
rg "<artifactId>(h2|mysql|postgresql|mariadb|oracle)" pom.xml
fi
echo -e "\n=== Checking build.gradle for database dependencies ==="
if [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
rg "(h2|mysql|postgresql|mariadb|oracle)" build.gradle* 2>/dev/null || true
fiRepository: mmije0ng/FarmON_BE_Refactor Length of output: 452 🏁 Script executed: #!/bin/bash
# Search more specifically for application configuration files in src directory
echo "=== Application configuration files in src ==="
find src -name "application*.properties" -o -name "application*.yml" -o -name "application*.yaml" | sort
echo -e "\n=== Content of test application properties ==="
find src -path "*test*" -name "application*.properties" -o -path "*test*" -name "application*.yml" -o -path "*test*" -name "application*.yaml" | xargs cat 2>/dev/null || echo "No test configs found"
echo -e "\n=== Main application configurations ==="
find src/main -name "application*.properties" -o -name "application*.yml" -o -name "application*.yaml" | xargs cat 2>/dev/null || echo "No main configs found"
echo -e "\n=== Checking if PostRepositoryImpl is tested ==="
fd -e java | xargs rg -l "PostRepositoryImpl|PostRepository" | grep -i test
echo -e "\n=== Check build.gradle for test profile ==="
cat build.gradle 2>/dev/null | grep -A 5 -B 5 "test\|h2" || echo "Could not read build.gradle"Repository: mmije0ng/FarmON_BE_Refactor Length of output: 2073 🏁 Script executed: #!/bin/bash
# Look at the PostRepositoryImpl file to understand the context better
echo "=== PostRepositoryImpl.java content around line 191-196 ==="
if [ -f "src/main/java/com/backend/farmon/repository/PostRepository/PostRepositoryImpl.java" ]; then
cat -n "src/main/java/com/backend/farmon/repository/PostRepository/PostRepositoryImpl.java" | sed -n '180,210p'
else
echo "File not found, searching for it..."
find . -name "PostRepositoryImpl.java" -type f
fi
echo -e "\n=== Check if there are test files for PostRepository ==="
find . -path "*test*" -name "*PostRepository*Test*.java" -o -path "*test*" -name "*Post*RepositoryTest.java"
echo -e "\n=== Look for TestApplication or test config ==="
find . -path "*test*" -name "TestApplication.java" -o -path "*test*" -name "Test*.java" | head -10
echo -e "\n=== Check for @DataJpaTest or repository test annotations ==="
rg "@DataJpaTest|@SpringBootTest|testcontainers" -t java | head -20Repository: mmije0ng/FarmON_BE_Refactor Length of output: 1640 🏁 Script executed: #!/bin/bash
# Check if there's a default test configuration when no explicit config exists
echo "=== Check for application.yml or application.properties in test resources ==="
ls -la src/test/resources/ 2>/dev/null || echo "test/resources not found"
echo -e "\n=== Look for spring profiles in test ==="
rg "spring.profiles.active|@ActiveProfiles" -t java
echo -e "\n=== Check FarmonApplicationTests.java ==="
cat -n src/test/java/com/backend/farmon/FarmonApplicationTests.java
echo -e "\n=== Search for any test that calls PostRepository methods ==="
rg -r "getPostsWithPopularByCategory|popularPostsIdList" --type=java src/test/
echo -e "\n=== Check Spring Boot default test database behavior ==="
grep -i "test.database\|datasource.url" src/test/resources/*.properties src/test/resources/*.yml 2>/dev/null || echo "No test datasource config found"
echo -e "\n=== Overall test directory structure ==="
find src/test -type f -name "*.java" -o -name "*.properties" -o -name "*.yml" | head -20Repository: mmije0ng/FarmON_BE_Refactor Length of output: 815 MySQL 프로젝트의
🤖 Prompt for AI Agents |
||
|
|
||
| query.orderBy( | ||
| pinnedFirstOrderExpr.asc(), | ||
| pinnedInnerOrderExpr.asc(), | ||
| likeCount.id.countDistinct().desc(), | ||
| post.createdAt.desc() | ||
| ); | ||
| } else { | ||
| query.orderBy( | ||
| likeCount.id.countDistinct().desc(), | ||
| post.createdAt.desc() | ||
| ); | ||
| } | ||
|
|
||
| return query | ||
| .limit(limit) | ||
| .fetch(); | ||
| } | ||
|
|
||
|
|
||
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.
🧹 Nitpick | 🔵 Trivial
캐시 대상 DTO로 적절한 구조입니다.
@NoArgsConstructor(Jackson 역직렬화용)와@AllArgsConstructor(QueryDSL projection용) 조합이 적절합니다.불변성을 보장하려면 필드에
final키워드를 추가하고@RequiredArgsConstructor대신 명시적 생성자를 사용하거나, Lombok의@Value와@Jacksonized를 조합하는 방안도 고려해 보세요. 현재 구조도 동작에 문제는 없습니다.🤖 Prompt for AI Agents