From b43e0c1c44caed006b5ebd0d20319a983c5da04b Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Sat, 13 Sep 2025 21:11:12 +0900 Subject: [PATCH 1/8] =?UTF-8?q?chore:=20queryDSL=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=84=A4=EC=B9=98=20=EB=B0=8F=20config=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20(#369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 22 ++++++++++++++++++- gradlew | 0 .../buddyya/common/config/QueryDslConfig.java | 19 ++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) mode change 100644 => 100755 gradlew create mode 100644 src/main/java/com/team/buddyya/common/config/QueryDslConfig.java diff --git a/build.gradle b/build.gradle index db04ad14..ca43328a 100644 --- a/build.gradle +++ b/build.gradle @@ -24,11 +24,31 @@ repositories { maven { url 'https://jitpack.io' } } +// QueryDSL +def generatedDir = "$buildDir/generated/querydsl" + +sourceSets { + main.java.srcDirs += [generatedDir] +} + +tasks.withType(JavaCompile) { + options.getGeneratedSourceOutputDirectory().set(file(generatedDir)) +} + +clean.doLast { + file(generatedDir).deleteDir() +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-batch' + // QueryDSL Dependencies + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' + annotationProcessor 'jakarta.annotation:jakarta.annotation-api' + annotationProcessor 'jakarta.persistence:jakarta.persistence-api' // 채팅 웹 소켓 implementation 'org.springframework.boot:spring-boot-starter-websocket' implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' @@ -42,7 +62,7 @@ dependencies { //Flyway implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' - + implementation 'com.fasterxml.jackson.core:jackson-databind' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' compileOnly 'org.projectlombok:lombok' diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/com/team/buddyya/common/config/QueryDslConfig.java b/src/main/java/com/team/buddyya/common/config/QueryDslConfig.java new file mode 100644 index 00000000..79f97dd5 --- /dev/null +++ b/src/main/java/com/team/buddyya/common/config/QueryDslConfig.java @@ -0,0 +1,19 @@ +package com.team.buddyya.common.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QueryDslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} From fe4168a04b2ae44bb2308366ae44026b860add3a Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Sat, 13 Sep 2025 21:14:38 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EC=BB=A4=EC=84=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=ED=94=BC=EB=93=9C=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20DTO=20=EA=B5=AC=ED=98=84=20(#369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/buddyya/feed/controller/FeedController.java | 12 ++++++++++++ .../feed/dto/request/feed/FeedCursorListRequest.java | 10 ++++++++++ .../feed/dto/response/feed/FeedListResponse.java | 10 ++++++++++ 3 files changed, 32 insertions(+) create mode 100644 src/main/java/com/team/buddyya/feed/dto/request/feed/FeedCursorListRequest.java diff --git a/src/main/java/com/team/buddyya/feed/controller/FeedController.java b/src/main/java/com/team/buddyya/feed/controller/FeedController.java index 5eefcf4a..091fc64e 100644 --- a/src/main/java/com/team/buddyya/feed/controller/FeedController.java +++ b/src/main/java/com/team/buddyya/feed/controller/FeedController.java @@ -2,6 +2,7 @@ import com.team.buddyya.auth.domain.CustomUserDetails; import com.team.buddyya.feed.dto.request.feed.FeedCreateRequest; +import com.team.buddyya.feed.dto.request.feed.FeedCursorListRequest; import com.team.buddyya.feed.dto.request.feed.FeedListRequest; import com.team.buddyya.feed.dto.request.feed.FeedUpdateRequest; import com.team.buddyya.feed.dto.response.BookmarkResponse; @@ -45,6 +46,17 @@ public ResponseEntity getFeeds(@AuthenticationPrincipal Custom return ResponseEntity.ok(response); } + @GetMapping("/cursor") + public ResponseEntity getFeedsByCursor( + @AuthenticationPrincipal CustomUserDetails userDetails, + @ModelAttribute FeedCursorListRequest request) { + FeedListResponse response = feedService.getFeedsByCursor( + userDetails.getStudentInfo(), + request + ); + return ResponseEntity.ok(response); + } + @GetMapping("/popular") public ResponseEntity getPopularFeeds( @AuthenticationPrincipal CustomUserDetails userDetails, diff --git a/src/main/java/com/team/buddyya/feed/dto/request/feed/FeedCursorListRequest.java b/src/main/java/com/team/buddyya/feed/dto/request/feed/FeedCursorListRequest.java new file mode 100644 index 00000000..faab077b --- /dev/null +++ b/src/main/java/com/team/buddyya/feed/dto/request/feed/FeedCursorListRequest.java @@ -0,0 +1,10 @@ +package com.team.buddyya.feed.dto.request.feed; + +public record FeedCursorListRequest( + String university, + String category, + String keyword, + Long lastId, + Integer pageSize +) { +} diff --git a/src/main/java/com/team/buddyya/feed/dto/response/feed/FeedListResponse.java b/src/main/java/com/team/buddyya/feed/dto/response/feed/FeedListResponse.java index 13111c8e..0627db26 100644 --- a/src/main/java/com/team/buddyya/feed/dto/response/feed/FeedListResponse.java +++ b/src/main/java/com/team/buddyya/feed/dto/response/feed/FeedListResponse.java @@ -3,6 +3,7 @@ import com.team.buddyya.feed.domain.Feed; import java.util.List; import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; public record FeedListResponse( List feeds, @@ -19,4 +20,13 @@ public static FeedListResponse from(List feeds, Page feedInf feedInfo.hasNext() ); } + + public static FeedListResponse from(List feeds, Slice sliceInfo) { + return new FeedListResponse( + feeds, + sliceInfo.getNumber(), + -1, + sliceInfo.hasNext() + ); + } } From 93600a58f414fab65edbd6e89fc1297e50fe0633 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Sat, 13 Sep 2025 21:20:05 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20Repos?= =?UTF-8?q?itory=20=EC=83=9D=EC=84=B1=20(#369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/buddyya/feed/repository/FeedRepository.java | 2 +- .../buddyya/feed/repository/FeedRepositoryCustom.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/team/buddyya/feed/repository/FeedRepositoryCustom.java diff --git a/src/main/java/com/team/buddyya/feed/repository/FeedRepository.java b/src/main/java/com/team/buddyya/feed/repository/FeedRepository.java index 2d2e3ea4..a0b08546 100644 --- a/src/main/java/com/team/buddyya/feed/repository/FeedRepository.java +++ b/src/main/java/com/team/buddyya/feed/repository/FeedRepository.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Repository; @Repository -public interface FeedRepository extends JpaRepository { +public interface FeedRepository extends JpaRepository, FeedRepositoryCustom { @Override @EntityGraph(attributePaths = { diff --git a/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryCustom.java b/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryCustom.java new file mode 100644 index 00000000..b1b11d28 --- /dev/null +++ b/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.team.buddyya.feed.repository; + +import com.team.buddyya.feed.domain.Feed; +import com.team.buddyya.feed.dto.request.feed.FeedCursorListRequest; +import org.springframework.data.domain.Slice; + +public interface FeedRepositoryCustom { + + Slice findFeedsByCursor(FeedCursorListRequest request, Feed lastFeed); +} From 0afc49705ae7776f436388c2c69fde5375acb188 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Sat, 13 Sep 2025 21:52:54 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=EB=8F=99=EC=A0=81=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=9D=B4=EC=9A=A9=ED=95=9C=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=8C=20=ED=94=BC=EB=93=9C=20=EC=B0=BE=EA=B8=B0=20(#369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/repository/FeedRepositoryImpl.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/main/java/com/team/buddyya/feed/repository/FeedRepositoryImpl.java diff --git a/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryImpl.java b/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryImpl.java new file mode 100644 index 00000000..82c97066 --- /dev/null +++ b/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryImpl.java @@ -0,0 +1,84 @@ +package com.team.buddyya.feed.repository; + +import static com.team.buddyya.feed.domain.QFeed.feed; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.team.buddyya.feed.domain.Feed; +import com.team.buddyya.feed.dto.request.feed.FeedCursorListRequest; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; + +@RequiredArgsConstructor +public class FeedRepositoryImpl implements FeedRepositoryCustom { + + private static final int DEFAULT_PAGE_SIZE = 10; + private final JPAQueryFactory queryFactory; + + @Override + public Slice findFeedsByCursor(FeedCursorListRequest request, Feed lastFeed) { + int pageSize = (request.pageSize() != null && request.pageSize() > 0) ? request.pageSize() : DEFAULT_PAGE_SIZE; + List feeds = queryFactory + .selectFrom(feed) + .where( + cursorCondition(lastFeed), + eqUniversity(request.university()), + eqCategory(request.category()), + containsKeyword(request.keyword()) + ) + .orderBy(feed.pinned.desc(), feed.createdDate.desc(), feed.id.desc()) + .limit(pageSize + 1) + .fetch(); + return toSlice(feeds, pageSize); + } + + private BooleanExpression cursorCondition(Feed lastFeed) { + if (lastFeed == null) { + return null; + } + BooleanExpression pinnedExpression = feed.pinned.coalesce(false); + boolean lastPinned = lastFeed.isPinned(); + LocalDateTime lastCreatedAt = lastFeed.getCreatedDate(); + Long lastId = lastFeed.getId(); + return pinnedExpression.lt(lastPinned) + .or(pinnedExpression.eq(lastPinned) + .and(feed.createdDate.lt(lastCreatedAt))) + .or(pinnedExpression.eq(lastPinned) + .and(feed.createdDate.eq(lastCreatedAt)) + .and(feed.id.lt(lastId))); + } + + private BooleanExpression eqUniversity(String university) { + if (university == null || university.isBlank() || university.equalsIgnoreCase("all")) { + return null; + } + return feed.university.universityName.eq(university); + } + + private BooleanExpression eqCategory(String category) { + if (category == null || category.isBlank()) { + return null; + } + return feed.category.name.eq(category); + } + + private BooleanExpression containsKeyword(String keyword) { + if (keyword == null || keyword.isBlank()) { + return null; + } + return feed.title.containsIgnoreCase(keyword) + .or(feed.content.containsIgnoreCase(keyword)); + } + + private Slice toSlice(List feeds, int pageSize) { + boolean hasNext = feeds.size() > pageSize; + if (hasNext) { + feeds.remove(pageSize); + } + return new SliceImpl<>(feeds, PageRequest.of(0, pageSize), hasNext); + } +} From a804c939e033fc406615b6e80ce1f2c7b577953b Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Sat, 13 Sep 2025 22:08:06 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=EC=BB=A4=EC=84=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=ED=94=BC=EB=93=9C=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../buddyya/feed/service/FeedService.java | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/team/buddyya/feed/service/FeedService.java b/src/main/java/com/team/buddyya/feed/service/FeedService.java index 9a7aafb8..b3a1ad30 100644 --- a/src/main/java/com/team/buddyya/feed/service/FeedService.java +++ b/src/main/java/com/team/buddyya/feed/service/FeedService.java @@ -8,6 +8,7 @@ import com.team.buddyya.feed.domain.FeedUserAction; import com.team.buddyya.feed.dto.projection.FeedAuthorInfo; import com.team.buddyya.feed.dto.request.feed.FeedCreateRequest; +import com.team.buddyya.feed.dto.request.feed.FeedCursorListRequest; import com.team.buddyya.feed.dto.request.feed.FeedListRequest; import com.team.buddyya.feed.dto.request.feed.FeedUpdateRequest; import com.team.buddyya.feed.dto.response.feed.FeedListResponse; @@ -38,6 +39,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -85,39 +87,30 @@ public FeedListResponse getFeeds(StudentInfo studentInfo, Pageable pageable, Fee Page feeds = (request.keyword() == null || request.keyword().isBlank()) ? getFeedsByUniversityAndCategory(request, pageable) : getFeedsByKeyword(student, request.keyword(), pageable); + return createFeedListResponse(feeds, studentInfo); + } - Set blockedStudentIds = blockRepository.findBlockedStudentIdByBlockerId(studentInfo.id()); - List feedIds = feeds.getContent().stream() - .map(Feed::getId) - .toList(); - Set likedFeedIds = feedLikeRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); - Set bookmarkedFeedIds = bookmarkRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); - Map authorInfoMap = getFeedAuthorInfoMap(feeds.getContent()); - List response = filterBlockedFeeds(feeds.getContent(), blockedStudentIds, student, likedFeedIds, - bookmarkedFeedIds, authorInfoMap); - return FeedListResponse.from(response, feeds); + @Transactional(readOnly = true) + public FeedListResponse getFeedsByCursor(StudentInfo studentInfo, FeedCursorListRequest request) { + final Feed lastFeed = request.lastId() != null ? + feedRepository.findById(request.lastId()) + .orElseThrow(() -> new FeedException(FeedExceptionType.FEED_NOT_FOUND)) + : null; + Slice feedSlice = feedRepository.findFeedsByCursor(request, lastFeed); + return createFeedListResponse(feedSlice, studentInfo); } + @Transactional(readOnly = true) public FeedListResponse getPopularFeeds( StudentInfo studentInfo, Pageable pageable, FeedListRequest request ) { - Student student = findStudentByStudentId(studentInfo.id()); University university = findUniversityByUniversityName(request.university()); Page feeds = feedRepository.findByLikeCountGreaterThanEqualAndUniversity(LIKE_COUNT_THRESHOLD, university, pageable); - Set blockedStudentIds = blockRepository.findBlockedStudentIdByBlockerId(studentInfo.id()); - List feedIds = feeds.getContent().stream() - .map(Feed::getId) - .toList(); - Set likedFeedIds = feedLikeRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); - Set bookmarkedFeedIds = bookmarkRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); - Map authorInfoMap = getFeedAuthorInfoMap(feeds.getContent()); - List response = filterBlockedFeeds(feeds.getContent(), blockedStudentIds, student, likedFeedIds, - bookmarkedFeedIds, authorInfoMap); - return FeedListResponse.from(response, feeds); + return createFeedListResponse(feeds, studentInfo); } @Transactional(readOnly = true) @@ -152,17 +145,7 @@ public FeedListResponse getBookmarkFeed(StudentInfo studentInfo, Pageable pageab Student student = findStudentByStudentId(studentInfo.id()); Page bookmarks = bookmarkRepository.findAllByStudent(student, pageable); Page feeds = bookmarks.map(Bookmark::getFeed); - Set blockedStudentIds = blockRepository.findBlockedStudentIdByBlockerId(studentInfo.id()); - List feedIds = feeds.getContent().stream() - .map(Feed::getId) - .toList(); - - Set likedFeedIds = feedLikeRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); - Set bookmarkedFeedIds = bookmarkRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); - Map authorInfoMap = getFeedAuthorInfoMap(feeds.getContent()); - List response = filterBlockedFeeds(feeds.getContent(), blockedStudentIds, student, likedFeedIds, - bookmarkedFeedIds, authorInfoMap); - return FeedListResponse.from(response, feeds); + return createFeedListResponse(feeds, studentInfo); } @Transactional(readOnly = true) @@ -232,6 +215,28 @@ public void togglePin(StudentInfo studentInfo, Long feedId) { feed.togglePin(); } + private FeedListResponse createFeedListResponse(Slice feedSlice, StudentInfo studentInfo) { + List feeds = feedSlice.getContent(); + Student student = findStudentByStudentId(studentInfo.id()); + if (feeds.isEmpty()) { + return FeedListResponse.from(List.of(), feedSlice); + } + Set blockedStudentIds = blockRepository.findBlockedStudentIdByBlockerId(studentInfo.id()); + List feedIds = feeds.stream().map(Feed::getId).toList(); + Set likedFeedIds = feedLikeRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); + Set bookmarkedFeedIds = bookmarkRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); + Map authorInfoMap = getFeedAuthorInfoMap(feeds); + List response = filterBlockedFeeds( + feeds, + blockedStudentIds, + student, + likedFeedIds, + bookmarkedFeedIds, + authorInfoMap + ); + return FeedListResponse.from(response, feedSlice); + } + private List filterBlockedFeeds(List feeds, Set blockedStudentIds, Student currentStudent, Set likedFeedIds, Set bookmarkedFeedIds, From e61e40e52426520a5a9c0602696804d4ab3e9c2e Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Sat, 13 Sep 2025 22:26:14 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20N=20+=201=20=EB=B2=8C=ED=81=AC?= =?UTF-8?q?=20=EB=A1=9C=EB=94=A9=EC=97=90=EC=84=9C=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=B6=84=EB=A6=AC=20(#369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/feed/FeedListResponse.java | 2 +- .../buddyya/feed/service/FeedService.java | 72 +++++++++---------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/team/buddyya/feed/dto/response/feed/FeedListResponse.java b/src/main/java/com/team/buddyya/feed/dto/response/feed/FeedListResponse.java index 0627db26..01c5d4bc 100644 --- a/src/main/java/com/team/buddyya/feed/dto/response/feed/FeedListResponse.java +++ b/src/main/java/com/team/buddyya/feed/dto/response/feed/FeedListResponse.java @@ -24,7 +24,7 @@ public static FeedListResponse from(List feeds, Page feedInf public static FeedListResponse from(List feeds, Slice sliceInfo) { return new FeedListResponse( feeds, - sliceInfo.getNumber(), + -1, -1, sliceInfo.hasNext() ); diff --git a/src/main/java/com/team/buddyya/feed/service/FeedService.java b/src/main/java/com/team/buddyya/feed/service/FeedService.java index b3a1ad30..af604c25 100644 --- a/src/main/java/com/team/buddyya/feed/service/FeedService.java +++ b/src/main/java/com/team/buddyya/feed/service/FeedService.java @@ -148,6 +148,39 @@ public FeedListResponse getBookmarkFeed(StudentInfo studentInfo, Pageable pageab return createFeedListResponse(feeds, studentInfo); } + private FeedListResponse createFeedListResponse(Page feedPage, StudentInfo studentInfo) { + List feedResponses = buildFeedResponses(feedPage.getContent(), studentInfo); + return FeedListResponse.from(feedResponses, feedPage); + } + + private FeedListResponse createFeedListResponse(Slice feedSlice, StudentInfo studentInfo) { + List feedResponses = buildFeedResponses(feedSlice.getContent(), studentInfo); + return FeedListResponse.from(feedResponses, feedSlice); + } + + private List buildFeedResponses(List feeds, StudentInfo studentInfo) { + if (feeds.isEmpty()) { + return List.of(); + } + Student student = findStudentByStudentId(studentInfo.id()); + Set blockedStudentIds = blockRepository.findBlockedStudentIdByBlockerId(studentInfo.id()); + List feedIds = feeds.stream().map(Feed::getId).toList(); + Set likedFeedIds = feedLikeRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); + Set bookmarkedFeedIds = bookmarkRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); + Map authorInfoMap = getFeedAuthorInfoMap(feeds); + return feeds.stream() + .filter(feed -> !blockedStudentIds.contains(feed.getStudent().getId())) + .map(feed -> { + boolean isFeedOwner = feed.isFeedOwner(student.getId()); + boolean isLiked = likedFeedIds.contains(feed.getId()); + boolean isBookmarked = bookmarkedFeedIds.contains(feed.getId()); + FeedUserAction userAction = FeedUserAction.from(isFeedOwner, isLiked, isBookmarked); + FeedAuthorInfo authorInfo = authorInfoMap.get(feed.getStudent().getId()); + return FeedResponse.from(feed, userAction, authorInfo); + }) + .toList(); + } + @Transactional(readOnly = true) Page getFeedsByKeyword(Student student, String keyword, Pageable pageable) { University openUniversity = findUniversityByUniversityName("all"); @@ -215,45 +248,6 @@ public void togglePin(StudentInfo studentInfo, Long feedId) { feed.togglePin(); } - private FeedListResponse createFeedListResponse(Slice feedSlice, StudentInfo studentInfo) { - List feeds = feedSlice.getContent(); - Student student = findStudentByStudentId(studentInfo.id()); - if (feeds.isEmpty()) { - return FeedListResponse.from(List.of(), feedSlice); - } - Set blockedStudentIds = blockRepository.findBlockedStudentIdByBlockerId(studentInfo.id()); - List feedIds = feeds.stream().map(Feed::getId).toList(); - Set likedFeedIds = feedLikeRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); - Set bookmarkedFeedIds = bookmarkRepository.findFeedIdsByStudentIdAndFeedIdsIn(studentInfo.id(), feedIds); - Map authorInfoMap = getFeedAuthorInfoMap(feeds); - List response = filterBlockedFeeds( - feeds, - blockedStudentIds, - student, - likedFeedIds, - bookmarkedFeedIds, - authorInfoMap - ); - return FeedListResponse.from(response, feedSlice); - } - - private List filterBlockedFeeds(List feeds, Set blockedStudentIds, - Student currentStudent, Set likedFeedIds, - Set bookmarkedFeedIds, - Map authorInfoMap) { - return feeds.stream() - .filter(feed -> !blockedStudentIds.contains(feed.getStudent().getId())) - .map(feed -> { - boolean isFeedOwner = feed.isFeedOwner(currentStudent.getId()); - boolean isLiked = likedFeedIds.contains(feed.getId()); - boolean isBookmarked = bookmarkedFeedIds.contains(feed.getId()); - FeedUserAction userAction = FeedUserAction.from(isFeedOwner, isLiked, isBookmarked); - FeedAuthorInfo authorInfo = authorInfoMap.get(feed.getStudent().getId()); - return FeedResponse.from(feed, userAction, authorInfo); - }) - .toList(); - } - private Map getFeedAuthorInfoMap(List feeds) { Set studentIds = feeds.stream() .map(feed -> feed.getStudent().getId()) From ee64f0730976eba41c8a8bcb84fb9e6301af6e23 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Sun, 14 Sep 2025 01:35:22 +0900 Subject: [PATCH 7/8] =?UTF-8?q?chore:=20flyway=20index=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EB=AC=B8=20(#369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/repository/FeedRepositoryImpl.java | 15 +++------------ .../db/migration/V32__Add_feed_index.sql | 1 + 2 files changed, 4 insertions(+), 12 deletions(-) create mode 100644 src/main/resources/db/migration/V32__Add_feed_index.sql diff --git a/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryImpl.java b/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryImpl.java index 82c97066..e0b30b09 100644 --- a/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryImpl.java +++ b/src/main/java/com/team/buddyya/feed/repository/FeedRepositoryImpl.java @@ -27,8 +27,7 @@ public Slice findFeedsByCursor(FeedCursorListRequest request, Feed lastFee .where( cursorCondition(lastFeed), eqUniversity(request.university()), - eqCategory(request.category()), - containsKeyword(request.keyword()) + eqCategory(request.category()) ) .orderBy(feed.pinned.desc(), feed.createdDate.desc(), feed.id.desc()) .limit(pageSize + 1) @@ -40,7 +39,7 @@ private BooleanExpression cursorCondition(Feed lastFeed) { if (lastFeed == null) { return null; } - BooleanExpression pinnedExpression = feed.pinned.coalesce(false); + BooleanExpression pinnedExpression = feed.pinned; boolean lastPinned = lastFeed.isPinned(); LocalDateTime lastCreatedAt = lastFeed.getCreatedDate(); Long lastId = lastFeed.getId(); @@ -53,7 +52,7 @@ private BooleanExpression cursorCondition(Feed lastFeed) { } private BooleanExpression eqUniversity(String university) { - if (university == null || university.isBlank() || university.equalsIgnoreCase("all")) { + if (university == null || university.isBlank()) { return null; } return feed.university.universityName.eq(university); @@ -66,14 +65,6 @@ private BooleanExpression eqCategory(String category) { return feed.category.name.eq(category); } - private BooleanExpression containsKeyword(String keyword) { - if (keyword == null || keyword.isBlank()) { - return null; - } - return feed.title.containsIgnoreCase(keyword) - .or(feed.content.containsIgnoreCase(keyword)); - } - private Slice toSlice(List feeds, int pageSize) { boolean hasNext = feeds.size() > pageSize; if (hasNext) { diff --git a/src/main/resources/db/migration/V32__Add_feed_index.sql b/src/main/resources/db/migration/V32__Add_feed_index.sql new file mode 100644 index 00000000..f708f251 --- /dev/null +++ b/src/main/resources/db/migration/V32__Add_feed_index.sql @@ -0,0 +1 @@ +CREATE INDEX idx_feed_pagination ON feed (university_id, category_id, pinned DESC, created_date DESC, id DESC); From b8ec5812a07b8cee9d0359db00d0fcd3427650c7 Mon Sep 17 00:00:00 2001 From: mangsuyo Date: Fri, 19 Sep 2025 15:23:35 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor:=20final=20=ED=82=A4=EC=9B=8C?= =?UTF-8?q?=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20(#369))?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/team/buddyya/feed/service/FeedService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/team/buddyya/feed/service/FeedService.java b/src/main/java/com/team/buddyya/feed/service/FeedService.java index af604c25..a0132fdf 100644 --- a/src/main/java/com/team/buddyya/feed/service/FeedService.java +++ b/src/main/java/com/team/buddyya/feed/service/FeedService.java @@ -92,7 +92,7 @@ public FeedListResponse getFeeds(StudentInfo studentInfo, Pageable pageable, Fee @Transactional(readOnly = true) public FeedListResponse getFeedsByCursor(StudentInfo studentInfo, FeedCursorListRequest request) { - final Feed lastFeed = request.lastId() != null ? + Feed lastFeed = request.lastId() != null ? feedRepository.findById(request.lastId()) .orElseThrow(() -> new FeedException(FeedExceptionType.FEED_NOT_FOUND)) : null; @@ -180,7 +180,7 @@ private List buildFeedResponses(List feeds, StudentInfo stud }) .toList(); } - + @Transactional(readOnly = true) Page getFeedsByKeyword(Student student, String keyword, Pageable pageable) { University openUniversity = findUniversityByUniversityName("all");