From c80d697548b5ca576419ab259d43a13233d8d953 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Fri, 15 Aug 2025 19:02:02 +0900 Subject: [PATCH 1/9] =?UTF-8?q?refactor:=20=EC=86=8C=EC=8B=9D=EC=A7=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20isLike=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인하지 않은 사용자에게는 isLike를 응답하지 않도록 --- .../example/solidconnection/news/dto/NewsResponse.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java index 77d8cd3a3..450af58d7 100644 --- a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java +++ b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java @@ -1,6 +1,9 @@ package com.example.solidconnection.news.dto; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; + import com.example.solidconnection.news.domain.News; +import com.fasterxml.jackson.annotation.JsonInclude; import java.time.ZonedDateTime; public record NewsResponse( @@ -9,16 +12,21 @@ public record NewsResponse( String description, String thumbnailUrl, String url, + + @JsonInclude(NON_NULL) + Boolean isLike, + ZonedDateTime updatedAt ) { - public static NewsResponse from(News news) { + public static NewsResponse from(News news, Boolean isLike) { return new NewsResponse( news.getId(), news.getTitle(), news.getDescription(), news.getThumbnailUrl(), news.getUrl(), + isLike, news.getUpdatedAt() ); } From 5260235c687772491ac43ed1d417d44470a6bddc Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Fri, 15 Aug 2025 19:03:51 +0900 Subject: [PATCH 2/9] =?UTF-8?q?refactor:=20=EC=86=8C=EC=8B=9D=EC=A7=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인한 사용자와 로그인하지 않은 사용자를 구분 - @AuthorizedUser(required = false) 활용 - Set을 활용하여 n + 1 문제 해결 --- .../news/controller/NewsController.java | 5 ++-- .../news/repository/LikedNewsRepository.java | 11 ++++++++ .../news/service/NewsQueryService.java | 28 +++++++++++++++++-- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/solidconnection/news/controller/NewsController.java b/src/main/java/com/example/solidconnection/news/controller/NewsController.java index 6ba7b2eda..43a59e378 100644 --- a/src/main/java/com/example/solidconnection/news/controller/NewsController.java +++ b/src/main/java/com/example/solidconnection/news/controller/NewsController.java @@ -37,9 +37,10 @@ public class NewsController { // todo: 추후 Slice 적용 @GetMapping public ResponseEntity findNewsBySiteUserId( - @RequestParam(value = "site-user-id") Long siteUserId + @AuthorizedUser(required = false) Long siteUserId, + @RequestParam(value = "author-id") Long authorId ) { - NewsListResponse newsListResponse = newsQueryService.findNewsBySiteUserId(siteUserId); + NewsListResponse newsListResponse = newsQueryService.findNewsByAuthorId(siteUserId, authorId); return ResponseEntity.ok(newsListResponse); } diff --git a/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java b/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java index 26e3d8e3c..c04d06e98 100644 --- a/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java +++ b/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java @@ -1,12 +1,23 @@ package com.example.solidconnection.news.repository; import com.example.solidconnection.news.domain.LikedNews; +import java.util.List; import java.util.Optional; +import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface LikedNewsRepository extends JpaRepository { boolean existsByNewsIdAndSiteUserId(long newsId, long siteUserId); Optional findByNewsIdAndSiteUserId(long newsId, long siteUserId); + + @Query(""" + SELECT l.newsId + FROM LikedNews l + WHERE l.newsId IN :newsIds AND l.siteUserId = :siteUserId + """) + Set findLikedNewsIdsByNewsIdsAndSiteUserId(@Param("newsIds") List newsIds, @Param("siteUserId") Long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java index cda55c1dd..8fc46afa6 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java @@ -3,8 +3,10 @@ import com.example.solidconnection.news.domain.News; import com.example.solidconnection.news.dto.NewsListResponse; import com.example.solidconnection.news.dto.NewsResponse; +import com.example.solidconnection.news.repository.LikedNewsRepository; import com.example.solidconnection.news.repository.NewsRepository; import java.util.List; +import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -14,13 +16,33 @@ public class NewsQueryService { private final NewsRepository newsRepository; + private final LikedNewsRepository likedNewsRepository; @Transactional(readOnly = true) - public NewsListResponse findNewsBySiteUserId(long siteUserId) { - List newsList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(siteUserId); + public NewsListResponse findNewsByAuthorId(Long siteUserId, long authorId) { + List newsList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(authorId); + + // 로그인하지 않은 경우 + if (siteUserId == null) { + List newsResponseList = newsList.stream() + .map(news -> NewsResponse.from(news, null)) + .toList(); + return NewsListResponse.from(newsResponseList); + } + + // 로그인한 경우 + List newsIds = newsList.stream() + .map(News::getId) + .toList(); + + Set likedNewsIds = likedNewsRepository.findLikedNewsIdsByNewsIdsAndSiteUserId(newsIds, siteUserId); List newsResponseList = newsList.stream() - .map(NewsResponse::from) + .map(news -> { + Boolean isLike = likedNewsIds.contains(news.getId()); + return NewsResponse.from(news, isLike); + }) .toList(); + return NewsListResponse.from(newsResponseList); } } From dbcde23c48194fbdee5a53409def391a002c9a7b Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Fri, 15 Aug 2025 19:04:08 +0900 Subject: [PATCH 3/9] =?UTF-8?q?test:=20=EC=86=8C=EC=8B=9D=EC=A7=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20fixture=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../news/fixture/LikedNewsFixture.java | 19 ++++++++++ .../news/fixture/LikedNewsFixtureBuilder.java | 36 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixture.java create mode 100644 src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixtureBuilder.java diff --git a/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixture.java b/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixture.java new file mode 100644 index 000000000..acacb25b0 --- /dev/null +++ b/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixture.java @@ -0,0 +1,19 @@ +package com.example.solidconnection.news.fixture; + +import com.example.solidconnection.news.domain.LikedNews; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class LikedNewsFixture { + + private final LikedNewsFixtureBuilder likedNewsFixtureBuilder; + + public LikedNews 소식지_좋아요(long newsId, long siteUserId) { + return likedNewsFixtureBuilder.likedNews() + .newsId(newsId) + .siteUserId(siteUserId) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixtureBuilder.java b/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixtureBuilder.java new file mode 100644 index 000000000..8554dc6b9 --- /dev/null +++ b/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixtureBuilder.java @@ -0,0 +1,36 @@ +package com.example.solidconnection.news.fixture; + +import com.example.solidconnection.news.domain.LikedNews; +import com.example.solidconnection.news.repository.LikedNewsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class LikedNewsFixtureBuilder { + + private final LikedNewsRepository likedNewsRepository; + + private long newsId; + + private long siteUserId; + + public LikedNewsFixtureBuilder likedNews() { + return new LikedNewsFixtureBuilder(likedNewsRepository); + } + + public LikedNewsFixtureBuilder newsId(long newsId) { + this.newsId = newsId; + return this; + } + + public LikedNewsFixtureBuilder siteUserId(long siteUserId) { + this.siteUserId = siteUserId; + return this; + } + + public LikedNews create() { + LikedNews likedNews = new LikedNews(newsId, siteUserId); + return likedNewsRepository.save(likedNews); + } +} From b036eb383fc97c1d955893c5ed46c03f38348f18 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Fri, 15 Aug 2025 19:04:20 +0900 Subject: [PATCH 4/9] =?UTF-8?q?test:=20=EC=86=8C=EC=8B=9D=EC=A7=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../news/service/NewsQueryServiceTest.java | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java index 12bfe2c59..c116fd8cd 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java @@ -6,12 +6,15 @@ import com.example.solidconnection.news.domain.News; import com.example.solidconnection.news.dto.NewsListResponse; import com.example.solidconnection.news.dto.NewsResponse; +import com.example.solidconnection.news.fixture.LikedNewsFixture; import com.example.solidconnection.news.fixture.NewsFixture; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -29,25 +32,66 @@ class NewsQueryServiceTest { @Autowired private NewsFixture newsFixture; + @Autowired + private LikedNewsFixture likedNewsFixture; + @Test - void 특정_사용자의_소식지_목록을_성공적으로_조회한다() { + void 로그인하지_않은_사용자가_특정_사용자의_소식지_목록을_성공적으로_조회한다() { // given - SiteUser user1 = siteUserFixture.멘토(1, "mentor1"); - SiteUser user2 = siteUserFixture.멘토(2, "mentor2"); - News news1 = newsFixture.소식지(user1.getId()); - News news2 = newsFixture.소식지(user1.getId()); - newsFixture.소식지(user2.getId()); + SiteUser author = siteUserFixture.멘토(1, "author"); + SiteUser otherUser = siteUserFixture.멘토(2, "other"); + + News news1 = newsFixture.소식지(author.getId()); + News news2 = newsFixture.소식지(author.getId()); + newsFixture.소식지(otherUser.getId()); List newsList = List.of(news1, news2); // when - NewsListResponse response = newsQueryService.findNewsBySiteUserId(user1.getId()); + NewsListResponse response = newsQueryService.findNewsByAuthorId(null, author.getId()); + + // then + assertAll( + () -> assertThat(response.newsResponseList()).hasSize(newsList.size()), + () -> assertThat(response.newsResponseList()) + .extracting(NewsResponse::updatedAt) + .isSortedAccordingTo(Comparator.reverseOrder()), + () -> assertThat(response.newsResponseList()) + .extracting(NewsResponse::isLike) + .containsOnly((Boolean) null) + ); + } + + @Test + void 로그인한_사용자가_특정_사용자의_소식지_목록을_성공적으로_조회한다() { + // given + SiteUser author = siteUserFixture.멘토(1, "author"); + SiteUser loginUser = siteUserFixture.멘토(2, "loginUser"); + + News news1 = newsFixture.소식지(author.getId()); + News news2 = newsFixture.소식지(author.getId()); + News news3 = newsFixture.소식지(author.getId()); + + likedNewsFixture.소식지_좋아요(news1.getId(), loginUser.getId()); + likedNewsFixture.소식지_좋아요(news3.getId(), loginUser.getId()); + + List newsList = List.of(news1, news2, news3); + + // when + NewsListResponse response = newsQueryService.findNewsByAuthorId(loginUser.getId(), author.getId()); // then assertAll( () -> assertThat(response.newsResponseList()).hasSize(newsList.size()), () -> assertThat(response.newsResponseList()) .extracting(NewsResponse::updatedAt) - .isSortedAccordingTo(Comparator.reverseOrder()) + .isSortedAccordingTo(Comparator.reverseOrder()), + () -> { + Map likeStatusMap = response.newsResponseList().stream() + .collect(Collectors.toMap(NewsResponse::id, NewsResponse::isLike)); + assertThat(likeStatusMap.get(news1.getId())).isTrue(); + assertThat(likeStatusMap.get(news2.getId())).isFalse(); + assertThat(likeStatusMap.get(news3.getId())).isTrue(); + } ); } } From 80634a185cc9e00ea803e777be637d3860239346 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Fri, 15 Aug 2025 19:07:16 +0900 Subject: [PATCH 5/9] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20api=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../news/controller/NewsController.java | 10 ------- .../news/dto/LikedNewsResponse.java | 10 ------- .../news/service/NewsLikeService.java | 10 ------- .../news/service/NewsLikeServiceTest.java | 26 ------------------- 4 files changed, 56 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java diff --git a/src/main/java/com/example/solidconnection/news/controller/NewsController.java b/src/main/java/com/example/solidconnection/news/controller/NewsController.java index 43a59e378..263124a18 100644 --- a/src/main/java/com/example/solidconnection/news/controller/NewsController.java +++ b/src/main/java/com/example/solidconnection/news/controller/NewsController.java @@ -1,7 +1,6 @@ package com.example.solidconnection.news.controller; import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.news.dto.LikedNewsResponse; import com.example.solidconnection.news.dto.NewsCommandResponse; import com.example.solidconnection.news.dto.NewsCreateRequest; import com.example.solidconnection.news.dto.NewsListResponse; @@ -81,15 +80,6 @@ public ResponseEntity deleteNewsById( return ResponseEntity.ok(newsCommandResponse); } - @GetMapping("/{news-id}/like") - public ResponseEntity isNewsLiked( - @AuthorizedUser long siteUserId, - @PathVariable("news-id") Long newsId - ) { - LikedNewsResponse likedNewsResponse = newsLikeService.isNewsLiked(siteUserId, newsId); - return ResponseEntity.ok(likedNewsResponse); - } - @PostMapping("/{news-id}/like") public ResponseEntity addNewsLike( @AuthorizedUser long siteUserId, diff --git a/src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java b/src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java deleted file mode 100644 index b854b9bf0..000000000 --- a/src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.solidconnection.news.dto; - -public record LikedNewsResponse( - boolean isLike -) { - - public static LikedNewsResponse of(boolean isLike) { - return new LikedNewsResponse(isLike); - } -} diff --git a/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java b/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java index e0e2e5114..4b9435ab6 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java @@ -6,7 +6,6 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.news.domain.LikedNews; -import com.example.solidconnection.news.dto.LikedNewsResponse; import com.example.solidconnection.news.repository.LikedNewsRepository; import com.example.solidconnection.news.repository.NewsRepository; import lombok.RequiredArgsConstructor; @@ -20,15 +19,6 @@ public class NewsLikeService { private final NewsRepository newsRepository; private final LikedNewsRepository likedNewsRepository; - @Transactional(readOnly = true) - public LikedNewsResponse isNewsLiked(long siteUserId, long newsId) { - if (!newsRepository.existsById(newsId)) { - throw new CustomException(NEWS_NOT_FOUND); - } - boolean isLike = likedNewsRepository.existsByNewsIdAndSiteUserId(newsId, siteUserId); - return LikedNewsResponse.of(isLike); - } - @Transactional public void addNewsLike(long siteUserId, long newsId) { if (!newsRepository.existsById(newsId)) { diff --git a/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java index e620fa206..2600c2891 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java @@ -7,7 +7,6 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.news.domain.News; -import com.example.solidconnection.news.dto.LikedNewsResponse; import com.example.solidconnection.news.fixture.NewsFixture; import com.example.solidconnection.news.repository.LikedNewsRepository; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -44,31 +43,6 @@ void setUp() { news = newsFixture.소식지(siteUserFixture.멘토(1, "mentor").getId()); } - @Nested - class 소식지_좋아요_상태를_조회한다 { - - @Test - void 좋아요한_소식지의_좋아요_상태를_조회한다() { - // given - newsLikeService.addNewsLike(user.getId(), news.getId()); - - // when - LikedNewsResponse response = newsLikeService.isNewsLiked(user.getId(), news.getId()); - - // then - assertThat(response.isLike()).isTrue(); - } - - @Test - void 좋아요하지_않은_소식지의_좋아요_상태를_조회한다() { - // when - LikedNewsResponse response = newsLikeService.isNewsLiked(user.getId(), news.getId()); - - // then - assertThat(response.isLike()).isFalse(); - } - } - @Nested class 소식지_좋아요를_등록한다 { From dd1873ec03ff157e983a2c0e2a1a325bd755ef5f Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Sun, 24 Aug 2025 12:06:38 +0900 Subject: [PATCH 6/9] =?UTF-8?q?refactor:=20=EC=BF=BC=EB=A6=AC=202=20->=201?= =?UTF-8?q?=EB=A1=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - jpql 프로젝션 활용 --- .../news/repository/LikedNewsRepository.java | 11 ------ .../news/repository/NewsRepository.java | 3 +- .../custom/NewsCustomRepository.java | 9 +++++ .../custom/NewsCustomRepositoryImpl.java | 38 +++++++++++++++++++ .../news/service/NewsQueryService.java | 21 ++-------- 5 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepository.java create mode 100644 src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepositoryImpl.java diff --git a/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java b/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java index c04d06e98..26e3d8e3c 100644 --- a/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java +++ b/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java @@ -1,23 +1,12 @@ package com.example.solidconnection.news.repository; import com.example.solidconnection.news.domain.LikedNews; -import java.util.List; import java.util.Optional; -import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; public interface LikedNewsRepository extends JpaRepository { boolean existsByNewsIdAndSiteUserId(long newsId, long siteUserId); Optional findByNewsIdAndSiteUserId(long newsId, long siteUserId); - - @Query(""" - SELECT l.newsId - FROM LikedNews l - WHERE l.newsId IN :newsIds AND l.siteUserId = :siteUserId - """) - Set findLikedNewsIdsByNewsIdsAndSiteUserId(@Param("newsIds") List newsIds, @Param("siteUserId") Long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java b/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java index 4ec1798df..0d3ccf3e9 100644 --- a/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java +++ b/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java @@ -1,10 +1,11 @@ package com.example.solidconnection.news.repository; import com.example.solidconnection.news.domain.News; +import com.example.solidconnection.news.repository.custom.NewsCustomRepository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -public interface NewsRepository extends JpaRepository { +public interface NewsRepository extends JpaRepository, NewsCustomRepository { List findAllBySiteUserIdOrderByUpdatedAtDesc(long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepository.java b/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepository.java new file mode 100644 index 000000000..ebba659e0 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepository.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.news.repository.custom; + +import com.example.solidconnection.news.dto.NewsResponse; +import java.util.List; + +public interface NewsCustomRepository { + + List findNewsByAuthorIdWithLikeStatus(long authorId, Long siteUserId); +} diff --git a/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepositoryImpl.java b/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepositoryImpl.java new file mode 100644 index 000000000..949d188bc --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepositoryImpl.java @@ -0,0 +1,38 @@ +package com.example.solidconnection.news.repository.custom; + +import com.example.solidconnection.news.dto.NewsResponse; +import jakarta.persistence.EntityManager; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class NewsCustomRepositoryImpl implements NewsCustomRepository { + + private final EntityManager entityManager; + + @Override + public List findNewsByAuthorIdWithLikeStatus(long authorId, Long siteUserId) { + String jpql = """ + SELECT new com.example.solidconnection.news.dto.NewsResponse( + n.id, + n.title, + n.description, + n.thumbnailUrl, + n.url, + CASE WHEN ln.id IS NOT NULL THEN true ELSE false END, + n.updatedAt + ) + FROM News n + LEFT JOIN LikedNews ln ON n.id = ln.newsId AND ln.siteUserId = :siteUserId + WHERE n.siteUserId = :authorId + ORDER BY n.updatedAt DESC + """; + + return entityManager.createQuery(jpql, NewsResponse.class) + .setParameter("authorId", authorId) + .setParameter("siteUserId", siteUserId) + .getResultList(); + } +} diff --git a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java index 8fc46afa6..daefa6e2c 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java @@ -1,12 +1,9 @@ package com.example.solidconnection.news.service; -import com.example.solidconnection.news.domain.News; import com.example.solidconnection.news.dto.NewsListResponse; import com.example.solidconnection.news.dto.NewsResponse; -import com.example.solidconnection.news.repository.LikedNewsRepository; import com.example.solidconnection.news.repository.NewsRepository; import java.util.List; -import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,32 +13,20 @@ public class NewsQueryService { private final NewsRepository newsRepository; - private final LikedNewsRepository likedNewsRepository; @Transactional(readOnly = true) public NewsListResponse findNewsByAuthorId(Long siteUserId, long authorId) { - List newsList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(authorId); - // 로그인하지 않은 경우 if (siteUserId == null) { - List newsResponseList = newsList.stream() + List newsResponseList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(authorId) + .stream() .map(news -> NewsResponse.from(news, null)) .toList(); return NewsListResponse.from(newsResponseList); } // 로그인한 경우 - List newsIds = newsList.stream() - .map(News::getId) - .toList(); - - Set likedNewsIds = likedNewsRepository.findLikedNewsIdsByNewsIdsAndSiteUserId(newsIds, siteUserId); - List newsResponseList = newsList.stream() - .map(news -> { - Boolean isLike = likedNewsIds.contains(news.getId()); - return NewsResponse.from(news, isLike); - }) - .toList(); + List newsResponseList = newsRepository.findNewsByAuthorIdWithLikeStatus(authorId, siteUserId); return NewsListResponse.from(newsResponseList); } From 452b4c776e8dec869d0ddd5fafeb83b5de690eba Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Sun, 24 Aug 2025 12:08:22 +0900 Subject: [PATCH 7/9] =?UTF-8?q?style:=20isLike=20->=20isLiked=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/solidconnection/news/dto/NewsResponse.java | 6 +++--- .../solidconnection/news/service/NewsQueryServiceTest.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java index 450af58d7..a10c68c74 100644 --- a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java +++ b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java @@ -14,19 +14,19 @@ public record NewsResponse( String url, @JsonInclude(NON_NULL) - Boolean isLike, + Boolean isLiked, ZonedDateTime updatedAt ) { - public static NewsResponse from(News news, Boolean isLike) { + public static NewsResponse from(News news, Boolean isLiked) { return new NewsResponse( news.getId(), news.getTitle(), news.getDescription(), news.getThumbnailUrl(), news.getUrl(), - isLike, + isLiked, news.getUpdatedAt() ); } diff --git a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java index c116fd8cd..9a20fea8a 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java @@ -56,7 +56,7 @@ class NewsQueryServiceTest { .extracting(NewsResponse::updatedAt) .isSortedAccordingTo(Comparator.reverseOrder()), () -> assertThat(response.newsResponseList()) - .extracting(NewsResponse::isLike) + .extracting(NewsResponse::isLiked) .containsOnly((Boolean) null) ); } @@ -87,7 +87,7 @@ class NewsQueryServiceTest { .isSortedAccordingTo(Comparator.reverseOrder()), () -> { Map likeStatusMap = response.newsResponseList().stream() - .collect(Collectors.toMap(NewsResponse::id, NewsResponse::isLike)); + .collect(Collectors.toMap(NewsResponse::id, NewsResponse::isLiked)); assertThat(likeStatusMap.get(news1.getId())).isTrue(); assertThat(likeStatusMap.get(news2.getId())).isFalse(); assertThat(likeStatusMap.get(news3.getId())).isTrue(); From c86ebacc8f1a19cd9caa99555b6ccc2239c529e9 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Sun, 24 Aug 2025 12:11:08 +0900 Subject: [PATCH 8/9] =?UTF-8?q?test:=20=EA=B2=80=EC=A6=9D=20=EA=B5=AC?= =?UTF-8?q?=EC=B2=B4=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사이즈가 아닌 실제 id가 존재하는지 확인하도록 --- .../news/service/NewsQueryServiceTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java index 9a20fea8a..6c69db0cf 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java @@ -51,7 +51,9 @@ class NewsQueryServiceTest { // then assertAll( - () -> assertThat(response.newsResponseList()).hasSize(newsList.size()), + () -> assertThat(response.newsResponseList()) + .extracting(NewsResponse::id) + .containsExactlyInAnyOrder(news1.getId(), news2.getId()), () -> assertThat(response.newsResponseList()) .extracting(NewsResponse::updatedAt) .isSortedAccordingTo(Comparator.reverseOrder()), @@ -81,7 +83,9 @@ class NewsQueryServiceTest { // then assertAll( - () -> assertThat(response.newsResponseList()).hasSize(newsList.size()), + () -> assertThat(response.newsResponseList()) + .extracting(NewsResponse::id) + .containsExactlyInAnyOrder(news1.getId(), news2.getId(), news3.getId()), () -> assertThat(response.newsResponseList()) .extracting(NewsResponse::updatedAt) .isSortedAccordingTo(Comparator.reverseOrder()), From 214be6b3d586961f89bb465a112999cf7faea9da Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Sun, 24 Aug 2025 17:51:30 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20from=20->=20of=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/solidconnection/news/dto/NewsResponse.java | 2 +- .../example/solidconnection/news/service/NewsQueryService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java index a10c68c74..d344080ba 100644 --- a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java +++ b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java @@ -19,7 +19,7 @@ public record NewsResponse( ZonedDateTime updatedAt ) { - public static NewsResponse from(News news, Boolean isLiked) { + public static NewsResponse of(News news, Boolean isLiked) { return new NewsResponse( news.getId(), news.getTitle(), diff --git a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java index daefa6e2c..e0050643b 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java @@ -20,7 +20,7 @@ public NewsListResponse findNewsByAuthorId(Long siteUserId, long authorId) { if (siteUserId == null) { List newsResponseList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(authorId) .stream() - .map(news -> NewsResponse.from(news, null)) + .map(news -> NewsResponse.of(news, null)) .toList(); return NewsListResponse.from(newsResponseList); }