-
Notifications
You must be signed in to change notification settings - Fork 0
fix: Projections.constructor 매핑 과정 오류 해결 #389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.example.green.domain.certification.infra.projections; | ||
|
|
||
| public interface MemberCertifiedCountProjection { | ||
| Long getMemberId(); | ||
|
|
||
| int getCertifiedCount(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.example.green.domain.certification.repository; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
|
|
||
| import com.example.green.domain.certification.domain.ChallengeCertification; | ||
| import com.example.green.domain.certification.infra.projections.MemberCertifiedCountProjection; | ||
|
|
||
| public interface ChallengeCertificationRepository extends JpaRepository<ChallengeCertification, Long> { | ||
|
|
||
| //단일 회원 인증 수 | ||
| int countChallengeCertificationByMemberMemberId(Long memberId); | ||
|
|
||
| //여러 회원 인증 수 (한번에 조회) | ||
| @Query("SELECT c.member.memberId AS memberId, COUNT(c) AS certifiedCount " + | ||
| "FROM ChallengeCertification c " + | ||
| "WHERE c.member.memberId IN :memberIds " + | ||
| "GROUP BY c.member.memberId") | ||
| List<MemberCertifiedCountProjection> findCertifiedCountByMemberIds(@Param("memberIds") List<Long> memberIds); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| import java.time.LocalDate; | ||
| import java.util.ArrayList; | ||
| import java.util.Comparator; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
|
|
@@ -12,6 +13,8 @@ | |
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import com.example.green.domain.certification.infra.projections.MemberCertifiedCountProjection; | ||
| import com.example.green.domain.certification.repository.ChallengeCertificationRepository; | ||
| import com.example.green.domain.dashboard.rankingmodule.dto.response.MemberPointResponse; | ||
| import com.example.green.domain.dashboard.rankingmodule.dto.response.TopMemberPointResponse; | ||
| import com.example.green.domain.dashboard.rankingmodule.entity.WeeklyRanking; | ||
|
|
@@ -20,6 +23,7 @@ | |
| import com.example.green.domain.dashboard.rankingmodule.repository.WeeklyRankingRepository; | ||
| import com.example.green.domain.member.entity.Member; | ||
| import com.example.green.domain.member.repository.MemberRepository; | ||
| import com.example.green.domain.point.repository.PointTransactionQueryRepository; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
@@ -32,34 +36,78 @@ public class WeeklyRankingService { | |
|
|
||
| private final WeeklyRankingRepository weeklyRankingRepository; | ||
| private final MemberRepository memberRepository; | ||
| private final PointTransactionQueryRepository pointTransactionQueryRepository; | ||
| private final ChallengeCertificationRepository challengeCertificationRepository; | ||
|
|
||
| //모든 주별 TopN 계산 로직 추가 | ||
| @Transactional | ||
| public void updateWeeklyRanks() { | ||
| public void updateWeeklyRanks(LocalDate weekStart, LocalDate weekEnd) { | ||
|
|
||
| List<WeeklyRanking> allWeeklyRankings = weeklyRankingRepository.findAllRankings(); | ||
| // 기존 주차 랭킹 삭제 | ||
| weeklyRankingRepository.deleteByWeekStart(weekStart); | ||
|
|
||
| Map<LocalDate, List<WeeklyRanking>> rankingsByWeek = allWeeklyRankings.stream() | ||
| .collect(Collectors.groupingBy(WeeklyRanking::getWeekStart)); | ||
|
|
||
| for (Map.Entry<LocalDate, List<WeeklyRanking>> entry : rankingsByWeek.entrySet()) { | ||
| // 모든 회원 조회 | ||
| List<Member> allMembers = memberRepository.findAll(); | ||
| List<Long> memberIds = allMembers.stream() | ||
| .map(Member::getId) | ||
| .toList(); | ||
|
|
||
| List<WeeklyRanking> weeklyRankings = entry.getValue(); | ||
| Map<Long, BigDecimal> memberPoints = Optional.ofNullable( | ||
| pointTransactionQueryRepository.findEarnedPointByMember(memberIds)) | ||
| .orElse(new HashMap<>()); | ||
|
|
||
| Map<Long, Integer> certifiedCounts = Optional.ofNullable( | ||
| challengeCertificationRepository.findCertifiedCountByMemberIds(memberIds)) | ||
| .orElse(new ArrayList<>()) | ||
| .stream() | ||
| .collect(Collectors.toMap( | ||
| MemberCertifiedCountProjection::getMemberId, | ||
| MemberCertifiedCountProjection::getCertifiedCount | ||
| )); | ||
|
|
||
| List<WeeklyRanking> weeklyRankings = allMembers.stream() | ||
| .map(member -> { | ||
| BigDecimal point = memberPoints.getOrDefault(member.getId(), BigDecimal.ZERO); | ||
| int certificationCount = certifiedCounts.getOrDefault(member.getId(), 0); | ||
| String profileImageUrl = (member.getProfile() != null) ? | ||
| member.getProfile().getProfileImageUrl() : null; | ||
|
|
||
| return WeeklyRanking.builder() | ||
| .memberId(member.getId()) | ||
| .memberName(member.getName()) | ||
| .profileImageUrl(profileImageUrl) | ||
| .totalPoint(point) // DB 저장/정렬용 | ||
| .certificationCount(certificationCount) | ||
| .weekStart(weekStart) | ||
| .weekEnd(weekEnd) | ||
| .rank(0) | ||
| .build(); | ||
| }) | ||
| .toList(); | ||
|
|
||
|
Comment on lines
+68
to
87
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cd /tmp && find . -name "pom.xml" -o -name "build.gradle" | head -5Repository: GreenWiNit/backend Length of output: 44 🏁 Script executed: cat -n /tmp/src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingService.java | head -120Repository: GreenWiNit/backend Length of output: 195 🏁 Script executed: git ls-files | grep -i "weeklyrankingservice"Repository: GreenWiNit/backend Length of output: 253 🏁 Script executed: git ls-files | grep -E "(pom\.xml|build\.gradle)" | head -5Repository: GreenWiNit/backend Length of output: 74 🏁 Script executed: cat build.gradle | grep -A 5 -B 5 "java\|source\|target\|version" | head -30Repository: GreenWiNit/backend Length of output: 895 🏁 Script executed: cat -n src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingService.java | sed -n '50,110p'Repository: GreenWiNit/backend Length of output: 2574 🌐 Web query:
💡 Result: Yes — Java 16 added Stream.toList(), and its Javadoc states the returned List is unmodifiable: "The returned List is unmodifiable; calls to any mutator method will always cause UnsupportedOperationException to be thrown." [1]. Note the difference from Collectors.toList() (no guarantees about mutability) and Collectors.toUnmodifiableList() (also unmodifiable but disallows nulls). [2] Sources:
On Java 16+, Replace with a mutable list: - List<WeeklyRanking> weeklyRankings = allMembers.stream()
+ List<WeeklyRanking> weeklyRankings = allMembers.stream()
.map(member -> {
BigDecimal point = memberPoints.getOrDefault(member.getId(), BigDecimal.ZERO);
int certificationCount = certifiedCounts.getOrDefault(member.getId(), 0);
String profileImageUrl = (member.getProfile() != null) ?
member.getProfile().getProfileImageUrl() : null;
return WeeklyRanking.builder()
.memberId(member.getId())
.memberName(member.getName())
.profileImageUrl(profileImageUrl)
.totalPoint(point) // DB 저장/정렬용
.certificationCount(certificationCount)
.weekStart(weekStart)
.weekEnd(weekEnd)
.rank(0)
.build();
- })
- .toList();
+ })
+ .collect(Collectors.toCollection(ArrayList::new));🤖 Prompt for AI Agents |
||
| weeklyRankings.sort( | ||
| Comparator.comparing(WeeklyRanking::getTotalPoint, Comparator.reverseOrder()) | ||
| .thenComparing(WeeklyRanking::getCertificationCount, Comparator.reverseOrder()) | ||
| ); | ||
| // 정렬 (포인트 내림차순, 동점이면 인증 수 내림차순) | ||
| weeklyRankings.sort( | ||
| Comparator.comparing(WeeklyRanking::getTotalPoint, Comparator.reverseOrder()) | ||
| .thenComparing(WeeklyRanking::getCertificationCount, Comparator.reverseOrder()) | ||
| ); | ||
|
|
||
| for (int i = 0; i < weeklyRankings.size(); i++) { | ||
| WeeklyRanking weeklyRanking = weeklyRankings.get(i); | ||
| int newRank = i + 1; | ||
| if (weeklyRanking.getRank() != newRank) { | ||
| weeklyRanking.setRank(newRank); | ||
| } | ||
| int rankCounter = 1; | ||
| for (int i = 0; i < weeklyRankings.size(); i++) { | ||
| if (i > 0 && | ||
| weeklyRankings.get(i).getTotalPoint().compareTo(weeklyRankings.get(i - 1).getTotalPoint()) == 0 && | ||
| weeklyRankings.get(i).getCertificationCount() == | ||
| weeklyRankings.get(i - 1).getCertificationCount()) { | ||
|
|
||
| // 완전 동점 → 이전 rank와 동일 | ||
| weeklyRankings.get(i).setRank(weeklyRankings.get(i - 1).getRank()); | ||
| } else { | ||
| weeklyRankings.get(i).setRank(rankCounter); | ||
| } | ||
| rankCounter++; | ||
| } | ||
|
|
||
| // DB 저장 | ||
| weeklyRankingRepository.saveAll(weeklyRankings); | ||
| } | ||
|
|
||
| public List<TopMemberPointResponse> getAllRankData(LocalDate weekStart, int topN) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle empty
memberIdslist edge case.If
memberIdsis empty, theIN :memberIdsclause may produce unexpected behavior or SQL errors depending on the database. Consider adding a guard in the service layer or using@Querywith conditional logic.Service-layer guard example:
🤖 Prompt for AI Agents