From 6b47e27aa38223c5deafb9053de5123ae0eda807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 8 Apr 2026 22:32:32 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=ED=8E=98=EC=9D=B4=EC=A7=95=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EB=8B=A8=EC=9D=BC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/util/KeywordExtractor.java | 75 ++++++++----------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/util/KeywordExtractor.java b/src/main/java/in/koreatech/koin/domain/community/util/KeywordExtractor.java index 2fce59b97..b972d1fb7 100644 --- a/src/main/java/in/koreatech/koin/domain/community/util/KeywordExtractor.java +++ b/src/main/java/in/koreatech/koin/domain/community/util/KeywordExtractor.java @@ -6,8 +6,6 @@ import java.util.Map; import java.util.stream.Collectors; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,56 +22,49 @@ @Transactional(readOnly = true) public class KeywordExtractor { - private static final int KEYWORD_BATCH_SIZE = 100; - private final ArticleKeywordRepository articleKeywordRepository; private final ArticleKeywordUserMapRepository articleKeywordUserMapRepository; public List matchKeyword(List
articles, Integer authorId) { - Map> matchedKeywordByUserIdByArticleId = new LinkedHashMap<>(); - int offset = 0; + List keywords = articleKeywordRepository.findAll(); - while (true) { - Pageable pageable = PageRequest.of(offset / KEYWORD_BATCH_SIZE, KEYWORD_BATCH_SIZE); - List keywords = articleKeywordRepository.findAll(pageable); + if (keywords.isEmpty()) { + return List.of(); + } - if (keywords.isEmpty()) { - break; - } - List keywordIds = keywords.stream() - .map(ArticleKeyword::getId) - .toList(); - Map> userMapsByKeywordId = articleKeywordUserMapRepository - .findAllByArticleKeywordIdIn(keywordIds) - .stream() - .filter(keywordUserMap -> !keywordUserMap.getIsDeleted()) - .collect(Collectors.groupingBy( - keywordUserMap -> keywordUserMap.getArticleKeyword().getId(), - LinkedHashMap::new, - Collectors.toList() - )); + List keywordIds = keywords.stream() + .map(ArticleKeyword::getId) + .toList(); + Map> userMapsByKeywordId = articleKeywordUserMapRepository + .findAllByArticleKeywordIdIn(keywordIds) + .stream() + .filter(keywordUserMap -> !keywordUserMap.getIsDeleted()) + .collect(Collectors.groupingBy( + keywordUserMap -> keywordUserMap.getArticleKeyword().getId(), + LinkedHashMap::new, + Collectors.toList() + )); - for (Article article : articles) { - String title = article.getTitle(); - for (ArticleKeyword keyword : keywords) { - if (!title.contains(keyword.getKeyword())) { - continue; - } - Map matchedKeywordByUserId = matchedKeywordByUserIdByArticleId - .computeIfAbsent(article.getId(), ignored -> new LinkedHashMap<>()); + Map> matchedKeywordByUserIdByArticleId = new LinkedHashMap<>(); + for (Article article : articles) { + String title = article.getTitle(); + for (ArticleKeyword keyword : keywords) { + if (!title.contains(keyword.getKeyword())) { + continue; + } + Map matchedKeywordByUserId = matchedKeywordByUserIdByArticleId + .computeIfAbsent(article.getId(), ignored -> new LinkedHashMap<>()); - for (ArticleKeywordUserMap keywordUserMap : - userMapsByKeywordId.getOrDefault(keyword.getId(), List.of())) { - Integer userId = keywordUserMap.getUser().getId(); - matchedKeywordByUserId.merge( - userId, - keyword.getKeyword(), - this::pickHigherPriorityKeyword - ); - } + for (ArticleKeywordUserMap keywordUserMap : + userMapsByKeywordId.getOrDefault(keyword.getId(), List.of())) { + Integer userId = keywordUserMap.getUser().getId(); + matchedKeywordByUserId.merge( + userId, + keyword.getKeyword(), + this::pickHigherPriorityKeyword + ); } } - offset += KEYWORD_BATCH_SIZE; } List keywordEvents = new ArrayList<>(); From 95140173771e0705676c83d510a0199de151bf1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 8 Apr 2026 22:32:43 +0900 Subject: [PATCH 2/4] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A9=A7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/domain/community/util/KeywordExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/util/KeywordExtractor.java b/src/main/java/in/koreatech/koin/domain/community/util/KeywordExtractor.java index b972d1fb7..4380a3653 100644 --- a/src/main/java/in/koreatech/koin/domain/community/util/KeywordExtractor.java +++ b/src/main/java/in/koreatech/koin/domain/community/util/KeywordExtractor.java @@ -9,10 +9,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import in.koreatech.koin.common.event.ArticleKeywordEvent; import in.koreatech.koin.domain.community.article.model.Article; import in.koreatech.koin.domain.community.keyword.model.ArticleKeyword; import in.koreatech.koin.domain.community.keyword.model.ArticleKeywordUserMap; -import in.koreatech.koin.common.event.ArticleKeywordEvent; import in.koreatech.koin.domain.community.keyword.repository.ArticleKeywordRepository; import in.koreatech.koin.domain.community.keyword.repository.ArticleKeywordUserMapRepository; import lombok.RequiredArgsConstructor; From 97bf224403c598825f16027849e3f1badf9e7d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 8 Apr 2026 22:32:53 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=8C=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/keyword/repository/ArticleKeywordRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/in/koreatech/koin/domain/community/keyword/repository/ArticleKeywordRepository.java b/src/main/java/in/koreatech/koin/domain/community/keyword/repository/ArticleKeywordRepository.java index b1e386601..56a06db38 100644 --- a/src/main/java/in/koreatech/koin/domain/community/keyword/repository/ArticleKeywordRepository.java +++ b/src/main/java/in/koreatech/koin/domain/community/keyword/repository/ArticleKeywordRepository.java @@ -30,6 +30,8 @@ public interface ArticleKeywordRepository extends Repository findAll(Pageable pageable); + List findAll(); + @Query(""" SELECT new in.koreatech.koin.domain.community.article.dto.ArticleKeywordResult(k.id, k.keyword, COUNT(u)) FROM ArticleKeywordUserMap u From 70b2b052d3a705907eb81605643bb5498ff4adcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 8 Apr 2026 22:43:51 +0900 Subject: [PATCH 4/4] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/util/KeywordExtractorTest.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/test/java/in/koreatech/koin/unit/domain/community/util/KeywordExtractorTest.java b/src/test/java/in/koreatech/koin/unit/domain/community/util/KeywordExtractorTest.java index 0c9c4f607..5d24ab6ef 100644 --- a/src/test/java/in/koreatech/koin/unit/domain/community/util/KeywordExtractorTest.java +++ b/src/test/java/in/koreatech/koin/unit/domain/community/util/KeywordExtractorTest.java @@ -15,7 +15,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.Pageable; import org.springframework.test.util.ReflectionTestUtils; import in.koreatech.koin.common.event.ArticleKeywordEvent; @@ -51,9 +50,8 @@ void matchKeyword_withMultipleMatchedKeywordsInSingleArticle_createsSingleEvent( ArticleKeyword keywordA = createKeyword(1, "근로", subscriber); ArticleKeyword keywordB = createKeyword(2, "근로장학", subscriber); - when(articleKeywordRepository.findAll(any(Pageable.class))) - .thenReturn(List.of(keywordA, keywordB)) - .thenReturn(List.of()); + when(articleKeywordRepository.findAll()) + .thenReturn(List.of(keywordA, keywordB)); when(articleKeywordUserMapRepository.findAllByArticleKeywordIdIn(any())) .thenReturn(List.of( keywordA.getArticleKeywordUserMaps().get(0), @@ -79,9 +77,8 @@ void matchKeyword_whenNoKeywordsMatch_returnsEmptyResult() { User subscriber = UserFixture.id_설정_코인_유저(1); ArticleKeyword keyword = createKeyword(1, "장학금", subscriber); - when(articleKeywordRepository.findAll(any(Pageable.class))) - .thenReturn(List.of(keyword)) - .thenReturn(List.of()); + when(articleKeywordRepository.findAll()) + .thenReturn(List.of(keyword)); when(articleKeywordUserMapRepository.findAllByArticleKeywordIdIn(any())) .thenReturn(List.of(keyword.getArticleKeywordUserMaps().get(0))); @@ -106,9 +103,8 @@ void matchKeyword_withMultipleArticles_createsEventPerArticle() { ArticleKeyword firstKeyword = createKeyword(1, "근로", firstSubscriber); ArticleKeyword secondKeyword = createKeyword(2, "장학금", secondSubscriber); - when(articleKeywordRepository.findAll(any(Pageable.class))) - .thenReturn(List.of(firstKeyword, secondKeyword)) - .thenReturn(List.of()); + when(articleKeywordRepository.findAll()) + .thenReturn(List.of(firstKeyword, secondKeyword)); when(articleKeywordUserMapRepository.findAllByArticleKeywordIdIn(any())) .thenReturn(List.of( firstKeyword.getArticleKeywordUserMaps().get(0), @@ -128,8 +124,7 @@ void matchKeyword_withMultipleArticles_createsEventPerArticle() { @DisplayName("등록된 키워드가 없으면 빈 결과를 반환한다.") void matchKeyword_whenNoKeywordsExist_returnsEmptyResult() { Article article = mock(Article.class); - when(article.getId()).thenReturn(1); - when(articleKeywordRepository.findAll(any(Pageable.class))).thenReturn(List.of()); + when(articleKeywordRepository.findAll()).thenReturn(List.of()); List result = keywordExtractor.matchKeyword(List.of(article), null);