From 5bc2b3d2488ce7adfdba8edc95a53bde23d628c6 Mon Sep 17 00:00:00 2001 From: joona95 Date: Mon, 10 Nov 2025 21:11:33 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=EC=9C=A0=ED=8A=9C=EB=B8=8C/?= =?UTF-8?q?=EB=B8=94=EB=A1=9C=EA=B7=B8=20=EB=A0=88=EC=8B=9C=ED=94=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=A0=80=EC=9E=A5=20=ED=9B=84?= =?UTF-8?q?=20=EC=9E=AC=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blog/BlogRecipeClientSearchService.java | 10 +++------- .../recipe/application/blog/BlogRecipeService.java | 14 ++++++-------- .../blog/BlogRecipeThumbnailCrawlingService.java | 3 +++ .../youtube/YoutubeRecipeClientSearchService.java | 11 ++++------- .../application/youtube/YoutubeRecipeService.java | 14 ++++++-------- 5 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeClientSearchService.java b/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeClientSearchService.java index fe8a825f..788dcc75 100644 --- a/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeClientSearchService.java +++ b/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeClientSearchService.java @@ -7,7 +7,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Map; @@ -38,7 +37,7 @@ public BlogRecipeClientSearchService(BlogRecipeRepository blogRecipeRepository, } @CircuitBreaker(name = "recipe-blog-search", fallbackMethod = "fallback") - public List searchNaverBlogRecipes(String keyword, int size) { + public void searchNaverBlogRecipes(String keyword) { log.info("naver blog search api call"); @@ -52,8 +51,6 @@ public List searchNaverBlogRecipes(String keyword, int size) { createBlogRecipes(blogRecipes); blogRecipeThumbnailCrawlingService.saveThumbnails(blogRecipes); - - return blogRecipes.subList(0, size); } public List fallback(String keyword, int size, Exception e) { @@ -63,12 +60,11 @@ public List fallback(String keyword, int size, Exception e) { return blogRecipeRepository.findByKeywordLimit(keyword, size); } - @Transactional - public void createBlogRecipes(List blogRecipes) { + private void createBlogRecipes(List blogRecipes) { List blogUrls = blogRecipes.stream().map(BlogRecipe::getBlogUrl).collect(Collectors.toList()); List existBlogRecipes = blogRecipeRepository.findByBlogUrlIn(blogUrls); - Map existBlogRecipeMapByBlogUrl = existBlogRecipes.stream().collect(Collectors.toMap(BlogRecipe::getBlogUrl, Function.identity())); + Map existBlogRecipeMapByBlogUrl = existBlogRecipes.stream().collect(Collectors.toMap(BlogRecipe::getBlogUrl, Function.identity(), (o1, o2) -> o1)); blogRecipeRepository.saveAll(blogRecipes.stream() .filter(blogRecipe -> !existBlogRecipeMapByBlogUrl.containsKey(blogRecipe.getBlogUrl())) diff --git a/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeService.java b/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeService.java index d3c08c79..6029e89c 100644 --- a/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeService.java +++ b/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeService.java @@ -33,6 +33,7 @@ public BlogRecipeService(BlogRecipeRepository blogRecipeRepository, BlogScrapSer this.blogRecipeClientSearchService = blogRecipeClientSearchService; } + @Transactional public RecipesResponse findBlogRecipesByKeyword(User user, String keyword, long lastBlogRecipeId, int size, String sort) { badWordFiltering.check(keyword); @@ -41,19 +42,16 @@ public RecipesResponse findBlogRecipesByKeyword(User user, String keyword, long List blogRecipes; if (totalCnt < MIN_RECIPE_CNT) { - - blogRecipes = blogRecipeClientSearchService.searchNaverBlogRecipes(keyword, size); - - totalCnt = blogRecipeRepository.countByKeyword(keyword); - } else { - blogRecipes = findByKeywordOrderBy(keyword, lastBlogRecipeId, size, sort); + blogRecipeClientSearchService.searchNaverBlogRecipes(keyword); } + blogRecipes = findByKeywordOrderBy(keyword, lastBlogRecipeId, size, sort); + totalCnt = blogRecipeRepository.countByKeyword(keyword); + return getRecipes(user, totalCnt, new BlogRecipes(blogRecipes)); } - @Transactional(readOnly = true) - public List findByKeywordOrderBy(String keyword, long lastBlogRecipeId, int size, String sort) { + private List findByKeywordOrderBy(String keyword, long lastBlogRecipeId, int size, String sort) { if (sort.equals("scraps")) { return findByKeywordOrderByBlogScrapCnt(keyword, lastBlogRecipeId, size); diff --git a/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeThumbnailCrawlingService.java b/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeThumbnailCrawlingService.java index 7de02e92..f243e45d 100644 --- a/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeThumbnailCrawlingService.java +++ b/src/main/java/com/recipe/app/src/recipe/application/blog/BlogRecipeThumbnailCrawlingService.java @@ -8,6 +8,8 @@ import org.jsoup.select.Elements; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import java.net.URL; import java.util.List; @@ -22,6 +24,7 @@ public BlogRecipeThumbnailCrawlingService(BlogRecipeRepository blogRecipeReposit } @Async + @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveThumbnails(List blogRecipes) { System.out.println("thumbnail save"); diff --git a/src/main/java/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeClientSearchService.java b/src/main/java/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeClientSearchService.java index 9296cee3..50071e01 100644 --- a/src/main/java/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeClientSearchService.java +++ b/src/main/java/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeClientSearchService.java @@ -15,7 +15,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.time.Instant; @@ -24,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; @Slf4j @@ -44,7 +44,7 @@ public YoutubeRecipeClientSearchService(YoutubeRecipeRepository youtubeRecipeRep } @CircuitBreaker(name = "recipe-youtube-search", fallbackMethod = "fallback") - public List searchYoutube(String keyword, int size) throws IOException { + public void searchYoutube(String keyword) throws IOException { log.info("youtube search api call"); @@ -80,8 +80,6 @@ public void initialize(HttpRequest request) throws IOException { } createYoutubeRecipes(youtubeRecipes); - - return youtubeRecipes.subList(0, size); } public List fallback(String keyword, int size, Exception e) { @@ -91,12 +89,11 @@ public List fallback(String keyword, int size, Exception e) { return youtubeRecipeRepository.findByKeywordLimit(keyword, size); } - @Transactional - public void createYoutubeRecipes(List youtubeRecipes) { + private void createYoutubeRecipes(List youtubeRecipes) { List youtubeIds = youtubeRecipes.stream().map(YoutubeRecipe::getYoutubeId).collect(Collectors.toList()); List existYoutubeRecipes = youtubeRecipeRepository.findByYoutubeIdIn(youtubeIds); - Map existYoutubeRecipesMapByYoutubeId = existYoutubeRecipes.stream().collect(Collectors.toMap(YoutubeRecipe::getYoutubeId, v -> v)); + Map existYoutubeRecipesMapByYoutubeId = existYoutubeRecipes.stream().collect(Collectors.toMap(YoutubeRecipe::getYoutubeId, Function.identity(), (o1, o2) -> o1)); youtubeRecipeRepository.saveAll(youtubeRecipes.stream() .filter(youtubeRecipe -> !existYoutubeRecipesMapByYoutubeId.containsKey(youtubeRecipe.getYoutubeId())) diff --git a/src/main/java/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeService.java b/src/main/java/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeService.java index 3690f9f1..82947ee6 100644 --- a/src/main/java/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeService.java +++ b/src/main/java/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeService.java @@ -32,6 +32,7 @@ public YoutubeRecipeService(YoutubeRecipeRepository youtubeRecipeRepository, You this.youtubeRecipeClientSearchService = youtubeRecipeClientSearchService; } + @Transactional public RecipesResponse findYoutubeRecipesByKeyword(User user, String keyword, long lastYoutubeRecipeId, int size, String sort) throws IOException { badWordFiltering.check(keyword); @@ -40,19 +41,16 @@ public RecipesResponse findYoutubeRecipesByKeyword(User user, String keyword, lo List youtubeRecipes; if (totalCnt < MIN_RECIPE_CNT) { - - youtubeRecipes = youtubeRecipeClientSearchService.searchYoutube(keyword, size); - - totalCnt = youtubeRecipeRepository.countByKeyword(keyword); - } else { - youtubeRecipes = findByKeywordOrderBy(keyword, lastYoutubeRecipeId, size, sort); + youtubeRecipeClientSearchService.searchYoutube(keyword); } + youtubeRecipes = findByKeywordOrderBy(keyword, lastYoutubeRecipeId, size, sort); + totalCnt = youtubeRecipeRepository.countByKeyword(keyword); + return getRecipes(user, totalCnt, new YoutubeRecipes(youtubeRecipes)); } - @Transactional(readOnly = true) - public List findByKeywordOrderBy(String keyword, long lastYoutubeRecipeId, int size, String sort) { + private List findByKeywordOrderBy(String keyword, long lastYoutubeRecipeId, int size, String sort) { if (sort.equals("scraps")) { return findByKeywordOrderByYoutubeScrapCnt(keyword, lastYoutubeRecipeId, size); From 4ab08e44ac6a8e4866f6a83752a3c3749076269f Mon Sep 17 00:00:00 2001 From: joona95 Date: Mon, 10 Nov 2025 21:26:13 +0900 Subject: [PATCH 2/2] =?UTF-8?q?test:=20=EC=9C=A0=ED=8A=9C=EB=B8=8C/?= =?UTF-8?q?=EB=B8=94=EB=A1=9C=EA=B7=B8=20=EB=A0=88=EC=8B=9C=ED=94=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=ED=95=84=EC=9A=94=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=99=B8=EB=B6=80=20API=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blog/BlogRecipeServiceTest.groovy | 68 ------------------- .../youtube/YoutubeRecipeServiceTest.groovy | 68 ------------------- 2 files changed, 136 deletions(-) diff --git a/src/test/groovy/com/recipe/app/src/recipe/application/blog/BlogRecipeServiceTest.groovy b/src/test/groovy/com/recipe/app/src/recipe/application/blog/BlogRecipeServiceTest.groovy index d876ee95..3e158245 100644 --- a/src/test/groovy/com/recipe/app/src/recipe/application/blog/BlogRecipeServiceTest.groovy +++ b/src/test/groovy/com/recipe/app/src/recipe/application/blog/BlogRecipeServiceTest.groovy @@ -230,74 +230,6 @@ class BlogRecipeServiceTest extends Specification { result.recipes.viewCnt == blogRecipes.viewCnt } - def "블로그 레시피 검색 - 외부 API 요청"() { - - given: - User user = User.builder() - .userId(1) - .socialId("naver_1") - .nickname("테스터1") - .build() - - String keyword = "테스트" - long lastBlogRecipeId = 0 - int size = 2 - String sort = "newest" - - blogRecipeRepository.countByKeyword(keyword) >> 5 - - List blogRecipes = [ - BlogRecipe.builder() - .blogRecipeId(1L) - .blogUrl("https://naver.com") - .blogThumbnailImgUrl("") - .title("제목") - .description("설명") - .publishedAt(LocalDate.now()) - .blogName("블로그명") - .scrapCnt(1) - .viewCnt(1) - .build(), - BlogRecipe.builder() - .blogRecipeId(2L) - .blogUrl("https://naver.com") - .blogThumbnailImgUrl("") - .title("제목") - .description("설명") - .publishedAt(LocalDate.now()) - .blogName("블로그명") - .scrapCnt(1) - .viewCnt(1) - .build(), - ] - - blogRecipeClientSearchService.searchNaverBlogRecipes(keyword, size) >> blogRecipes - - List blogScraps = [ - BlogScrap.builder() - .blogScrapId(1) - .userId(user.userId) - .blogRecipeId(blogRecipes.get(0).blogRecipeId) - .build() - ] - - blogScrapService.findByBlogRecipeIds(blogRecipes.blogRecipeId) >> blogScraps - - when: - RecipesResponse result = blogRecipeService.findBlogRecipesByKeyword(user, keyword, lastBlogRecipeId, size, sort) - - then: - result.totalCnt == 5 - result.recipes.recipeId == blogRecipes.blogRecipeId - result.recipes.recipeName == blogRecipes.title - result.recipes.introduction == blogRecipes.description - result.recipes.thumbnailImgUrl == blogRecipes.blogThumbnailImgUrl - result.recipes.postDate == [blogRecipes.get(0).publishedAt.format(DateTimeFormatter.ofPattern("yyyy.M.d")), blogRecipes.get(1).publishedAt.format(DateTimeFormatter.ofPattern("yyyy.M.d"))] - result.recipes.isUserScrap == [true, false] - result.recipes.scrapCnt == blogRecipes.scrapCnt - result.recipes.viewCnt == blogRecipes.viewCnt - } - def "스크랩한 블로그 레시피 목록 조회"() { given: diff --git a/src/test/groovy/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeServiceTest.groovy b/src/test/groovy/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeServiceTest.groovy index b6c55173..26f1eaab 100644 --- a/src/test/groovy/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeServiceTest.groovy +++ b/src/test/groovy/com/recipe/app/src/recipe/application/youtube/YoutubeRecipeServiceTest.groovy @@ -233,74 +233,6 @@ class YoutubeRecipeServiceTest extends Specification { result.recipes.viewCnt == youtubeRecipes.viewCnt } - def "유튜브 레시피 검색 - 외부 API 요청"() { - - given: - User user = User.builder() - .userId(1) - .socialId("naver_1") - .nickname("테스터1") - .build() - - String keyword = "테스트" - long lastYoutubeRecipeId = 0 - int size = 2 - String sort = "newest" - - youtubeRecipeRepository.countByKeyword(keyword) >> 5 - - List youtubeRecipes = [ - YoutubeRecipe.builder() - .youtubeRecipeId(1) - .title("제목") - .description("설명") - .thumbnailImgUrl("http://img.jpg") - .postDate(LocalDate.now()) - .channelName("채널명") - .youtubeId("abcdef") - .scrapCnt(1) - .viewCnt(1) - .build(), - YoutubeRecipe.builder() - .youtubeRecipeId(2) - .title("제목") - .description("설명") - .thumbnailImgUrl("http://img.jpg") - .postDate(LocalDate.now()) - .channelName("채널명") - .youtubeId("abcdef") - .scrapCnt(1) - .viewCnt(1) - .build() - ] - - youtubeRecipeClientSearchService.searchYoutube(keyword, size) >> youtubeRecipes - - List youtubeScraps = [ - YoutubeScrap.builder() - .youtubeScrapId(1) - .userId(user.userId) - .youtubeRecipeId(youtubeRecipes.get(0).youtubeRecipeId) - .build() - ] - - youtubeScrapService.findByYoutubeRecipeIds(youtubeRecipes.youtubeRecipeId) >> youtubeScraps - - when: - RecipesResponse result = youtubeRecipeService.findYoutubeRecipesByKeyword(user, keyword, lastYoutubeRecipeId, size, sort) - - then: - result.totalCnt == 5 - result.recipes.recipeId == youtubeRecipes.youtubeRecipeId - result.recipes.recipeName == youtubeRecipes.title - result.recipes.introduction == youtubeRecipes.description - result.recipes.thumbnailImgUrl == youtubeRecipes.thumbnailImgUrl - result.recipes.postDate == [youtubeRecipes.get(0).postDate.format(DateTimeFormatter.ofPattern("yyyy.M.d")), youtubeRecipes.get(1).postDate.format(DateTimeFormatter.ofPattern("yyyy.M.d"))] - result.recipes.isUserScrap == [true, false] - result.recipes.scrapCnt == youtubeRecipes.scrapCnt - result.recipes.viewCnt == youtubeRecipes.viewCnt - } - def "스크랩한 유튜브 레시피 목록 조회"() { given: