diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/controller/challengegroup/review/ChallengeReviewController.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/controller/challengegroup/review/ChallengeReviewController.java index adcc6a8..c3fdd9e 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/controller/challengegroup/review/ChallengeReviewController.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/controller/challengegroup/review/ChallengeReviewController.java @@ -11,7 +11,10 @@ import org.haedal.zzansuni.controller.user.UserRes; import org.haedal.zzansuni.core.api.ApiResponse; import org.haedal.zzansuni.domain.challengegroup.challenge.ChallengeService; +import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewModel.ChallengeReviewWithChallenge; +import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewModel.ChallengeReviewWithUserInfo; import org.haedal.zzansuni.global.jwt.JwtUser; +import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -22,68 +25,56 @@ @RestController @RequiredArgsConstructor public class ChallengeReviewController { + private final ChallengeService challengeService; @Operation(summary = "챌린지 그룹 최근 리뷰 페이징", description = "챌린지 최근 리뷰 페이징 조회.") @GetMapping("/api/challengeGroups/reviews") public ApiResponse> getChallengeReviews( - @Valid PagingRequest pagingRequest + @Valid PagingRequest pagingRequest + //TODO SORTING ) { - return ApiResponse.success( - PagingResponse.builder() - .hasNext(false) - .totalPage(1) - .data(List.of( - new ChallengeReviewRes.ChallengeReviewDto( - 1L, "title", - new UserRes.UserDto( - 1L, "nickname", "https://picsum.photos/200/300", new UserRes.TierInfoDto( - "tier", 100, 50 - )), - "content", 12 - - ) - )) - .build() + Page page = challengeService.getChallengeReviews( + pagingRequest.toPageable()); + + PagingResponse response = PagingResponse.from( + page, ChallengeReviewRes.ChallengeReviewDto::from ); + + return ApiResponse.success(response); + } @Operation(summary = "챌린지 그룹 리뷰 페이징", description = "챌린지 그룹 하위의 모든 챌린지 리뷰 페이징 조회.") @GetMapping("/api/challengeGroups/{challengeGroupId}/reviews") public ApiResponse> getChallengeReviewsPaging( - @PathVariable Long challengeGroupId, - @Valid PagingRequest pagingRequest - //TODO SORTING + @PathVariable Long challengeGroupId, + @Valid PagingRequest pagingRequest + //TODO SORTING ) { - return ApiResponse.success(PagingResponse.builder() - .hasNext(false) - .totalPage(1) - .data(List.of( - new ChallengeReviewRes.ChallengeReviewWithChalengeDto( - 1L, "title",12, - new UserRes.UserDto( - 1L, "nickname", "https://picsum.photos/200/300", new UserRes.TierInfoDto( - "tier", 100, 50 - )), - "content", 12 - - ) - )) - .build()); + Page page = challengeService.getChallengeReviewsByGroupId( + challengeGroupId, pagingRequest.toPageable()); + + PagingResponse response = PagingResponse.from( + page, ChallengeReviewRes.ChallengeReviewWithChalengeDto::from + ); + + return ApiResponse.success(response); + } @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "챌린지 리뷰 작성", description = "챌린지 리뷰를 작성한다.") @PostMapping("/api/challenges/{challengeId}/reviews") public ApiResponse challengeReviewCreate( - @PathVariable Long challengeId, - @AuthenticationPrincipal JwtUser jwtUser, - @RequestBody ChallengeReq.ChallengeReviewCreateRequest request + @PathVariable Long challengeId, + @AuthenticationPrincipal JwtUser jwtUser, + @RequestBody ChallengeReq.ChallengeReviewCreateRequest request ) { Long response = challengeService.createReview(request.toCommand(), challengeId, - jwtUser.getId()); + jwtUser.getId()); return ApiResponse.success(response, "챌린지 리뷰 작성에 성공하였습니다."); } diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/controller/challengegroup/review/ChallengeReviewRes.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/controller/challengegroup/review/ChallengeReviewRes.java index bf9fbac..4708ad5 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/controller/challengegroup/review/ChallengeReviewRes.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/controller/challengegroup/review/ChallengeReviewRes.java @@ -2,30 +2,56 @@ import lombok.Builder; import org.haedal.zzansuni.controller.user.UserRes; +import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewModel.ChallengeReviewWithChallenge; +import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewModel.ChallengeReviewWithUserInfo; public class ChallengeReviewRes { @Builder public record ChallengeReviewDto( - Long challengeId, - String challengeTitle, - UserRes.UserDto user, - String content, - Integer rating + Long challengeId, + String challengeTitle, + UserRes.UserDto user, + String content, + Integer rating ) { + public static ChallengeReviewDto from( + ChallengeReviewWithUserInfo challengeReviewWithUserInfo) { + var user = UserRes.UserDto.from(challengeReviewWithUserInfo.user()); + return ChallengeReviewDto.builder() + .challengeId(challengeReviewWithUserInfo.challengeId()) + .challengeTitle(challengeReviewWithUserInfo.challengeTitle()) + .user(user) + .content(challengeReviewWithUserInfo.content()) + .rating(challengeReviewWithUserInfo.rating()) + .build(); + } + } @Builder public record ChallengeReviewWithChalengeDto( - Long challengeId, - String challengeTitle, - Integer challengeDifficulty, - UserRes.UserDto user, - String content, - Integer rating + Long challengeId, + String challengeTitle, + Integer challengeDifficulty, + UserRes.UserDto user, + String content, + Integer rating ) { + public static ChallengeReviewWithChalengeDto from( + ChallengeReviewWithChallenge challengeReviewWithChallenge) { + var user = UserRes.UserDto.from(challengeReviewWithChallenge.user()); + return ChallengeReviewWithChalengeDto.builder() + .challengeId(challengeReviewWithChallenge.challengeId()) + .challengeTitle(challengeReviewWithChallenge.challengeTitle()) + .challengeDifficulty(challengeReviewWithChallenge.challengeDifficulty()) + .user(user) + .content(challengeReviewWithChallenge.content()) + .rating(challengeReviewWithChallenge.rating()) + .build(); + } } } diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/challenge/ChallengeService.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/challenge/ChallengeService.java index 777745a..1c17b6f 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/challenge/ChallengeService.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/challenge/ChallengeService.java @@ -6,6 +6,9 @@ import org.haedal.zzansuni.domain.challengegroup.ChallengeGroup; import org.haedal.zzansuni.domain.challengegroup.challenge.ChallengeModel.ChallengeRecord; import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReview; +import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewModel; +import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewModel.ChallengeReviewWithChallenge; +import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewModel.ChallengeReviewWithUserInfo; import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewReader; import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewStore; import org.haedal.zzansuni.domain.challengegroup.verification.ChallengeVerification; @@ -13,6 +16,8 @@ import org.haedal.zzansuni.domain.challengegroup.verification.ChallengeVerificationReader; import org.haedal.zzansuni.domain.challengegroup.userchallenge.UserChallenge; import org.haedal.zzansuni.domain.challengegroup.userchallenge.UserChallengeReader; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -65,17 +70,41 @@ public Long createReview(ChallengeCommand.ReviewCreate command, Long challengeId UserChallenge userChallenge = userChallengeReader.getByUserIdAndChallengeId(userId, challengeId); Long challengeGroupId = userChallenge - .getChallenge() - .getChallengeGroup() - .getId(); + .getChallenge() + .getChallengeGroup() + .getId(); //이미 리뷰를 작성했는지 확인 - challengeReviewReader.findByUserChallengeId(challengeId) + challengeReviewReader.findByUserChallengeId(userChallenge.getId()) .ifPresent(review -> { throw new IllegalArgumentException("이미 리뷰를 작성했습니다."); }); - ChallengeReview challengeReview = ChallengeReview.create(userChallenge, command, challengeGroupId); + ChallengeReview challengeReview = ChallengeReview.create(userChallenge, command, + challengeGroupId); challengeReviewStore.store(challengeReview); return challengeReview.getId(); } + + /** + * groupId로 챌린지 리뷰 가져오기 + */ + @Transactional(readOnly = true) + public Page getChallengeReviewsByGroupId( + Long challengeGroupId, Pageable pageable) { + Page challengeReviewPage = challengeReviewReader.getChallengeReviewPageByChallengeGroupId( + challengeGroupId, pageable); + + return challengeReviewPage.map(ChallengeReviewWithChallenge::from); + } + + /** + * 챌린지 리뷰 가져오기 + */ + @Transactional(readOnly = true) + public Page getChallengeReviews(Pageable pageable) { + Page challengeReviewPage = challengeReviewReader.getChallengeReviewPage( + pageable); + + return challengeReviewPage.map(ChallengeReviewWithUserInfo::from); + } } diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/review/ChallengeReviewModel.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/review/ChallengeReviewModel.java new file mode 100644 index 0000000..fd34510 --- /dev/null +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/review/ChallengeReviewModel.java @@ -0,0 +1,58 @@ +package org.haedal.zzansuni.domain.challengegroup.review; + +import lombok.Builder; +import org.haedal.zzansuni.domain.user.UserModel; + +public class ChallengeReviewModel { + + @Builder + public record ChallengeReviewWithUserInfo( + Long challengeId, + String challengeTitle, + UserModel user, + String content, + Integer rating + ) { + + public static ChallengeReviewWithUserInfo from(ChallengeReview challengeReview) { + var userModel = UserModel.from(challengeReview.getUserChallenge().getUser()); + return ChallengeReviewWithUserInfo.builder() + .challengeId(challengeReview.getUserChallenge().getChallenge().getId()) + .challengeTitle( + challengeReview.getUserChallenge().getChallenge().getChallengeGroup() + .getTitle()) + .user(userModel) + .content(challengeReview.getContent()) + .rating(challengeReview.getRating()) + .build(); + } + } + + @Builder + public record ChallengeReviewWithChallenge( + Long challengeId, + String challengeTitle, + Integer challengeDifficulty, + UserModel user, + String content, + Integer rating + ) { + + public static ChallengeReviewWithChallenge from(ChallengeReview challengeReview) { + var userModel = UserModel.from(challengeReview.getUserChallenge().getUser()); + return ChallengeReviewWithChallenge.builder() + .challengeId(challengeReview.getUserChallenge().getChallenge().getId()) + .challengeTitle( + challengeReview.getUserChallenge().getChallenge().getChallengeGroup() + .getTitle()) + .challengeDifficulty( + challengeReview.getUserChallenge().getChallenge().getDifficulty()) + .user(userModel) + .content(challengeReview.getContent()) + .rating(challengeReview.getRating()) + .build(); + } + + } + +} diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/review/ChallengeReviewReader.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/review/ChallengeReviewReader.java index 8ed088c..554754e 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/review/ChallengeReviewReader.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/review/ChallengeReviewReader.java @@ -3,12 +3,20 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; public interface ChallengeReviewReader { - ChallengeReview getByChallengeId(Long challengeId); + ChallengeReview getByUserChallengeId(Long userChallengeId); - Optional findByUserChallengeId(Long challengeId); + Optional findByUserChallengeId(Long userChallengeId); Map getReviewWrittenMapByUserChallengeId(List userChallengeIds); + + Page getChallengeReviewPageByChallengeGroupId(Long challengeGroupId, + Pageable pageable); + + Page getChallengeReviewPage(Pageable pageable); + } diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/userchallenge/UserChallengeService.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/userchallenge/UserChallengeService.java index 68658ed..8aa5d07 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/userchallenge/UserChallengeService.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/domain/challengegroup/userchallenge/UserChallengeService.java @@ -90,6 +90,7 @@ public Page getCurrentChallenges(Long userId, /** * 완료한 챌린지 페이징 조회 */ + @Transactional(readOnly = true) public Page getCompleteChallenges(Long userId, Pageable pageable) { Page userChallengePage = userChallengeReader diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/infrastructure/challengegroup/challengereview/ChallengeReviewReaderImpl.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/infrastructure/challengegroup/challengereview/ChallengeReviewReaderImpl.java index 8f514fb..51ce56e 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/infrastructure/challengegroup/challengereview/ChallengeReviewReaderImpl.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/infrastructure/challengegroup/challengereview/ChallengeReviewReaderImpl.java @@ -1,5 +1,6 @@ package org.haedal.zzansuni.infrastructure.challengegroup.challengereview; +import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -8,16 +9,21 @@ import lombok.RequiredArgsConstructor; import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReview; import org.haedal.zzansuni.domain.challengegroup.review.ChallengeReviewReader; +import org.haedal.zzansuni.domain.challengegroup.review.QChallengeReview; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor public class ChallengeReviewReaderImpl implements ChallengeReviewReader { + private final JPAQueryFactory queryFactory; private final ChallengeReviewRepository challengeReviewRepository; @Override - public ChallengeReview getByChallengeId(Long challengeId) { + public ChallengeReview getByUserChallengeId(Long challengeId) { return challengeReviewRepository.findByUserChallengeId(challengeId) .orElseThrow(NoSuchElementException::new); } @@ -46,4 +52,58 @@ public Map getReviewWrittenMapByUserChallengeId(List userCh return reviewWrittenMap; } + /** + * 챌린지 그룹 N+1 문제 해결 못함 + */ + @Override + public Page getChallengeReviewPageByChallengeGroupId(Long challengeGroupId, + Pageable pageable) { + Long count = queryFactory + .select(QChallengeReview.challengeReview.count()) + .from(QChallengeReview.challengeReview) + .where(QChallengeReview.challengeReview.challengeGroupId.eq(challengeGroupId)) + .fetchOne(); + + List challengeReviews = queryFactory + .select(QChallengeReview.challengeReview) + .from(QChallengeReview.challengeReview) + .where(QChallengeReview.challengeReview.challengeGroupId.eq(challengeGroupId)) + .leftJoin(QChallengeReview.challengeReview.userChallenge) + .fetchJoin() // userChallenge 엔티티 fetch join + .leftJoin(QChallengeReview.challengeReview.userChallenge.user) + .fetchJoin() // user 엔티티 fetch join + .leftJoin(QChallengeReview.challengeReview.userChallenge.challenge) + .fetchJoin() // challenge 엔티티 fetch join + .orderBy(QChallengeReview.challengeReview.createdAt.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + return new PageImpl<>(challengeReviews, pageable, count == null ? 0 : count); + + } + + @Override + public Page getChallengeReviewPage(Pageable pageable) { + Long count = queryFactory + .select(QChallengeReview.challengeReview.count()) + .from(QChallengeReview.challengeReview) + .fetchOne(); + + List challengeReviews = queryFactory + .select(QChallengeReview.challengeReview) + .from(QChallengeReview.challengeReview) + .leftJoin(QChallengeReview.challengeReview.userChallenge) + .fetchJoin() // userChallenge 엔티티 fetch join + .leftJoin(QChallengeReview.challengeReview.userChallenge.user) + .fetchJoin() // user 엔티티 fetch join + .leftJoin(QChallengeReview.challengeReview.userChallenge.challenge) + .fetchJoin() // challenge 엔티티 fetch join + .orderBy(QChallengeReview.challengeReview.createdAt.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + return new PageImpl<>(challengeReviews, pageable, count == null ? 0 : count); + } } diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/infrastructure/challengegroup/userchallenge/UserChallengeReaderImpl.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/infrastructure/challengegroup/userchallenge/UserChallengeReaderImpl.java index ecde738..416ec70 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/infrastructure/challengegroup/userchallenge/UserChallengeReaderImpl.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/infrastructure/challengegroup/userchallenge/UserChallengeReaderImpl.java @@ -67,7 +67,8 @@ public Page getCurrentChallengePageByUserId(Long userId, Pageable List userChallenges = queryFactory .selectFrom(QUserChallenge.userChallenge) .where(eqUserId(userId), eqProceeding()) - .join(QUserChallenge.userChallenge.challenge).fetchJoin() // manyToOne 관계 + .join(QUserChallenge.userChallenge.challenge). + fetchJoin() // manyToOne 관계 .join(QUserChallenge.userChallenge.challenge.challengeGroup) .fetchJoin() // manyToOne의 manyToOne 관계 엔티티를 가져옴 .leftJoin(QUserChallenge.userChallenge.challengeVerifications) @@ -96,7 +97,8 @@ public Page getCompletedChallengePageByUserId(Long userId, Pageab List userChallenges = queryFactory .selectFrom(QUserChallenge.userChallenge) .where(eqUserId(userId), neProceeding()) - .join(QUserChallenge.userChallenge.challenge).fetchJoin() // manyToOne 관계 + .join(QUserChallenge.userChallenge.challenge). + fetchJoin() // manyToOne 관계 .join(QUserChallenge.userChallenge.challenge.challengeGroup) .fetchJoin() // manyToOne의 manyToOne 관계 엔티티를 가져옴 .leftJoin(QUserChallenge.userChallenge.challengeVerifications)