From f7e3f1e529567dd8a98c35c245e248ecbe0d8a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=90=ED=99=8D=EC=84=9D?= <78216059+bayy1216@users.noreply.github.com> Date: Sun, 15 Sep 2024 00:31:48 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[Feat]:=20=EB=A6=AC=EB=B7=B0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20DB=20=EC=BF=BC=EB=A6=AC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ChallengeReviewModel.java | 10 ++ .../domain/ChallengeReviewReader.java | 2 + .../domain/ChallengeReviewService.java | 5 +- .../ChallengeReviewReaderImpl.java | 26 +++- .../ChallengeReviewReaderImplTest.java | 120 ++++++++++++++++++ 5 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImplTest.java diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewModel.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewModel.java index ebc0cb8..a07f875 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewModel.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewModel.java @@ -90,6 +90,16 @@ public static Score of(List challengeReviews) { .ratingCount(ratingCount) .build(); } + + public static Score from(Map ratingCount) { + var averageRating = ratingCount.entrySet().stream() + .mapToDouble(entry -> entry.getKey() * entry.getValue()) + .sum() / ratingCount.values().stream().mapToInt(Integer::intValue).sum(); + return Score.builder() + .averageRating((float) averageRating) + .ratingCount(ratingCount) + .build(); + } } } diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewReader.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewReader.java index c1a1355..d2845b7 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewReader.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewReader.java @@ -23,4 +23,6 @@ Page getChallengeReviewPageByChallengeGroupId(Long challengeGro List findByChallengeGroupId(Long challengeGroupId); + ChallengeReviewModel.Score getScoreModelByChallengeGroupId(Long challengeGroupId); + } diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewService.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewService.java index 1394d70..a9795d9 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewService.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReviewService.java @@ -67,9 +67,6 @@ public Page getChallengeReview @Transactional(readOnly = true) public ChallengeReviewModel.Score getChallengeGroupReviewScore( Long challengeGroupId) { - List challengeReviews = challengeReviewReader.findByChallengeGroupId( - challengeGroupId); - //TODO 모든 리뷰를 가져와서 계산 -> 성능 이슈 발생 가능 - return ChallengeReviewModel.Score.of(challengeReviews); + return challengeReviewReader.getScoreModelByChallengeGroupId(challengeGroupId); } } diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImpl.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImpl.java index 96db0e5..e2dee04 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImpl.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImpl.java @@ -1,8 +1,10 @@ package org.haedal.zzansuni.challengereview.infrastructure; +import com.querydsl.core.Tuple; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.haedal.zzansuni.challengereview.domain.ChallengeReview; +import org.haedal.zzansuni.challengereview.domain.ChallengeReviewModel; import org.haedal.zzansuni.challengereview.domain.ChallengeReviewReader; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -56,7 +58,7 @@ public Map getReviewWrittenMapByUserChallengeId(List userCh */ @Override public Page getChallengeReviewPageByChallengeGroupId(Long challengeGroupId, - Pageable pageable) { + Pageable pageable) { Long count = queryFactory .select(challengeReview.count()) .from(challengeReview) @@ -114,4 +116,26 @@ public List findByChallengeGroupId(Long challengeGroupId) { .where(challengeReview.challengeGroupId.eq(challengeGroupId)) .fetch(); } + + @Override + public ChallengeReviewModel.Score getScoreModelByChallengeGroupId(Long challengeGroupId) { + List fetch = queryFactory + .select(challengeReview.rating, challengeReview.count()) + .from(challengeReview) + .where(challengeReview.challengeGroupId.eq(challengeGroupId)) + .groupBy(challengeReview.rating) + .fetch(); + Map ratingCount = fetch + .stream() + .collect( + HashMap::new, + (m, v) -> + m.put(v.get(challengeReview.rating), v.get(challengeReview.count()).intValue()), + HashMap::putAll + ); + for(int i = 1; i <= 5; i++) { + ratingCount.putIfAbsent(i, 0); + } + return ChallengeReviewModel.Score.from(ratingCount); + } } diff --git a/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImplTest.java b/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImplTest.java new file mode 100644 index 0000000..e15c156 --- /dev/null +++ b/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImplTest.java @@ -0,0 +1,120 @@ +package org.haedal.zzansuni.challengereview.infrastructure; + +import org.haedal.zzansuni.challengegroup.domain.Challenge; +import org.haedal.zzansuni.challengegroup.domain.ChallengeCategory; +import org.haedal.zzansuni.challengegroup.domain.ChallengeGroup; +import org.haedal.zzansuni.challengegroup.infrastructure.ChallengeGroupRepository; +import org.haedal.zzansuni.challengegroup.infrastructure.ChallengeRepository; +import org.haedal.zzansuni.challengereview.domain.ChallengeReview; +import org.haedal.zzansuni.challengereview.domain.ChallengeReviewModel; +import org.haedal.zzansuni.global.security.Role; +import org.haedal.zzansuni.user.domain.User; +import org.haedal.zzansuni.user.infrastructure.UserRepository; +import org.haedal.zzansuni.userchallenge.domain.ChallengeStatus; +import org.haedal.zzansuni.userchallenge.domain.UserChallenge; +import org.haedal.zzansuni.userchallenge.infrastructure.UserChallengeRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.context.SpringBootTest; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class ChallengeReviewReaderImplTest { + @Autowired private ChallengeGroupRepository challengeGroupRepository; + @Autowired private UserRepository userRepository; + @Autowired private ChallengeRepository challengeRepository; + @Autowired private UserChallengeRepository userChallengeRepository; + @Autowired private ChallengeReviewRepository challengeReviewRepository; + @Autowired private ChallengeReviewReaderImpl challengeReviewReader; + + @Test + @DisplayName("리뷰 평점 count 쿼리가 정상적으로 동작한다.") + void getScoreModelByChallengeGroupId() { + // given + ChallengeGroup challengeGroup = createChallengeGroup(); + Challenge challenge = Challenge.builder() + .challengeGroup(challengeGroup) + .requiredCount(2) + .onceExp(100) + .successExp(100) + .difficulty(2) + .activePeriod(10) + .build(); + User user = createUser(); + UserChallenge userChallenge = createUserChallenge(challenge, user); + + challengeGroupRepository.save(challengeGroup); + challengeRepository.save(challenge); + userRepository.save(user); + userChallengeRepository.save(userChallenge); + + List reviews = IntStream.range(0, 11) + .mapToObj(i -> ChallengeReview.builder() + .challengeGroupId(challenge.getChallengeGroupId()) + .userChallenge(userChallenge) + .content("content") + .rating(i % 4 + 1) + .build()) + .toList(); + // -> 1:3 / 2:3 / 3:3 / 4:2 / 5:0 + challengeReviewRepository.saveAll(reviews); + + // when + ChallengeReviewModel.Score score = challengeReviewReader.getScoreModelByChallengeGroupId(1L); + + + // then + assertAll( + () -> assertEquals(3, score.ratingCount().get(1)), + () -> assertEquals(3, score.ratingCount().get(2)), + () -> assertEquals(3, score.ratingCount().get(3)), + () -> assertEquals(2, score.ratingCount().get(4)), + () -> assertEquals(0, score.ratingCount().get(5)) + ); + } + + private User createUser() { + return User.builder() + .nickname("nickname") + .email(null) + .password(null) + .role(Role.USER) + .provider(null) + .authToken(null) + .exp(0) + .profileImageUrl(null) + .build(); + } + + private ChallengeGroup createChallengeGroup() { + return ChallengeGroup.builder() + .title("title") + .category(ChallengeCategory.VOLUNTEER) + .content("content") + .guide("guide") + .cumulativeCount(0) + .joinStartDate(LocalDate.of(2023, 8, 1)) + .joinEndDate(LocalDate.of(2023, 8, 5)) + .build(); + } + + private UserChallenge createUserChallenge(Challenge challenge, User user) { + + return UserChallenge.builder() + .challenge(challenge) + .status(ChallengeStatus.PROCEEDING) + .activeStartDate(LocalDate.of(2023, 8, 1)) + .activeEndDate(LocalDate.of(2023, 8, 5)) + .user(user) + .challengeVerifications(new ArrayList<>()) + .build(); + } +} \ No newline at end of file From 2ed8343a80f42fe89579154a0f36b6bec4404631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=90=ED=99=8D=EC=84=9D?= <78216059+bayy1216@users.noreply.github.com> Date: Sun, 15 Sep 2024 00:34:06 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[Feat]:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=82=AD=EC=A0=9C=20(#58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChallengeReviewReaderImplTest.java | 2 ++ .../domain/application/RecordServiceTest.java | 13 ------------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImplTest.java b/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImplTest.java index e15c156..b8cc3bd 100644 --- a/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImplTest.java +++ b/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/challengereview/infrastructure/ChallengeReviewReaderImplTest.java @@ -18,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.ArrayList; @@ -36,6 +37,7 @@ class ChallengeReviewReaderImplTest { @Autowired private ChallengeReviewReaderImpl challengeReviewReader; @Test + @Transactional @DisplayName("리뷰 평점 count 쿼리가 정상적으로 동작한다.") void getScoreModelByChallengeGroupId() { // given diff --git a/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/userchallenge/domain/application/RecordServiceTest.java b/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/userchallenge/domain/application/RecordServiceTest.java index 804703a..6f50f18 100644 --- a/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/userchallenge/domain/application/RecordServiceTest.java +++ b/zzansuni-api-server/app/src/test/java/org/haedal/zzansuni/userchallenge/domain/application/RecordServiceTest.java @@ -207,17 +207,4 @@ void getChallengeReviews() { verify(challengeReviewReader).getChallengeReviewPage(pageable); } - @Test - void getChallengeGroupReviewScore() { - Long challengeGroupId = 1L; - List challengeReviews = Collections.singletonList(challengeReview); - when(challengeReviewReader.findByChallengeGroupId(challengeGroupId)).thenReturn( - challengeReviews); - - ChallengeReviewModel.Score result = challengeReviewService.getChallengeGroupReviewScore( - challengeGroupId); - - assertNotNull(result); - verify(challengeReviewReader).findByChallengeGroupId(challengeGroupId); - } } From 15f66e2e74b63d18ff52f77888b0b06a2fe307b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=90=ED=99=8D=EC=84=9D?= <78216059+bayy1216@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:16:30 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[Feat]:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20DDL=20=EC=88=98=EC=A0=95=20(#58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../zzansuni/challengereview/domain/ChallengeReview.java | 5 ++++- .../src/main/resources/db/migration/V3__alter_review.sql | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 zzansuni-api-server/app/src/main/resources/db/migration/V3__alter_review.sql diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReview.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReview.java index feb9fa7..6eae2ef 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReview.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/challengereview/domain/ChallengeReview.java @@ -12,7 +12,10 @@ @Builder @Table( indexes = { - @Index(name = "idx_challenge_review_challenge_group_id", columnList = "challenge_group_id"), + @Index( + name = "idx_challenge_review_challenge_group_id_rating", + columnList = "challenge_group_id, rating" + ), } ) public class ChallengeReview extends BaseTimeEntity { diff --git a/zzansuni-api-server/app/src/main/resources/db/migration/V3__alter_review.sql b/zzansuni-api-server/app/src/main/resources/db/migration/V3__alter_review.sql new file mode 100644 index 0000000..793bf45 --- /dev/null +++ b/zzansuni-api-server/app/src/main/resources/db/migration/V3__alter_review.sql @@ -0,0 +1,8 @@ +-- 평점 1~5이므로 `TINYINT`로 변경 +ALTER TABLE challenge_review MODIFY COLUMN rating TINYINT NOT NULL; + +-- 기존 인덱스 삭제 +DROP INDEX idx_challenge_review_challenge_group_id ON challenge_review; + +-- 새로운 복합 인덱스 생성 +CREATE INDEX idx_challenge_review_challenge_group_id_rating ON challenge_review (challenge_group_id, rating); \ No newline at end of file