Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.dokdok.gathering.repository;

public interface GatheringCountProjection {
Long getGatheringId();
Long getCount();
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,17 @@ int countMembersByStatus(
@Param("gatheringId") Long gatheringId,
@Param("status") GatheringMemberStatus status
);

/**
* 여러 모임의 ACTIVE 멤버 수를 한번에 조회
*/
@Query("SELECT gm.gathering.id AS gatheringId, COUNT(gm) AS count " +
"FROM GatheringMember gm " +
"WHERE gm.gathering.id IN :gatheringIds " +
"AND gm.memberStatus = 'ACTIVE' " +
"AND gm.removedAt IS NULL " +
"GROUP BY gm.gathering.id")
List<GatheringCountProjection> countActiveMembersByGatherings(
@Param("gatheringIds") List<Long> gatheringIds
);
}
65 changes: 55 additions & 10 deletions src/main/java/com/dokdok/gathering/service/GatheringService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.dokdok.gathering.entity.*;
import com.dokdok.gathering.exception.GatheringErrorCode;
import com.dokdok.gathering.exception.GatheringException;
import com.dokdok.gathering.repository.GatheringCountProjection;
import com.dokdok.gathering.repository.GatheringMemberRepository;
import com.dokdok.gathering.repository.GatheringRepository;
import com.dokdok.global.response.CursorResponse;
Expand Down Expand Up @@ -129,12 +130,19 @@ public FavoriteGatheringListResponse getFavoriteGatherings() {

List<GatheringMember> favoriteMembers = gatheringMemberRepository.findFavoriteGatheringsByUserId(userId);

List<Long> gatheringIds = favoriteMembers.stream()
.map(gm -> gm.getGathering().getId())
.toList();

Map<Long, Integer> memberCountMap = getActiveMemberCountMap(gatheringIds);
Map<Long, Integer> meetingCountMap = getMeetingCountMap(gatheringIds);

List<GatheringListItemResponse> gatheringResponses = favoriteMembers.stream()
.map(gatheringMember -> {
Gathering gathering = gatheringMember.getGathering();
int totalMembers = getActiveMemberCount(gathering.getId());
int totalMeetings = getMeetingCount(gathering.getId());
return GatheringListItemResponse.from(gatheringMember, totalMembers, totalMeetings, gatheringMember.getRole());
.map(gm -> {
Long gatheringId = gm.getGathering().getId();
int totalMembers = memberCountMap.getOrDefault(gatheringId, 0);
int totalMeetings = meetingCountMap.getOrDefault(gatheringId, 0);
return GatheringListItemResponse.from(gm, totalMembers, totalMeetings, gm.getRole());
})
.toList();

Expand Down Expand Up @@ -163,12 +171,20 @@ public CursorResponse<GatheringListItemResponse, MyGatheringCursor> getMyGatheri
boolean hasNext = members.size() > pageSize;
List<GatheringMember> pageMembers = hasNext ? members.subList(0, pageSize) : members;

List<Long> gatheringIds = pageMembers.stream()
.map(gm -> gm.getGathering().getId())
.toList();

Map<Long, Integer> memberCountMap = getActiveMemberCountMap(gatheringIds);
Map<Long, Integer> meetingCountMap = getMeetingCountMap(gatheringIds);

List<GatheringListItemResponse> items = pageMembers.stream()
.map(gm -> GatheringListItemResponse.from(
gm,
getActiveMemberCount(gm.getGathering().getId()),
getMeetingCount(gm.getGathering().getId()),
gm.getRole()))
.map(gm ->{
Long gatheringId = gm.getGathering().getId();
int totalMembers = memberCountMap.getOrDefault(gatheringId, 0);
int totalMeetings = meetingCountMap.getOrDefault(gatheringId, 0);
return GatheringListItemResponse.from(gm, totalMembers, totalMeetings, gm.getRole());
})
.toList();

GatheringMember lastMember = pageMembers.isEmpty() ? null : pageMembers.get(pageMembers.size() - 1);
Expand Down Expand Up @@ -419,4 +435,33 @@ private int getMeetingCount(Long gatheringId) {
return meetingRepository.countByGatheringIdAndMeetingStatus(gatheringId, MeetingStatus.DONE);
}

/**
* 여러 모임의 활성 멤버 수를 Map으로 조회
*/
private Map<Long, Integer> getActiveMemberCountMap(List<Long> gatheringIds) {
if(gatheringIds.isEmpty()) {
return Map.of();
}
return gatheringMemberRepository.countActiveMembersByGatherings(gatheringIds)
.stream()
.collect(Collectors.toMap(
GatheringCountProjection::getGatheringId,
p -> p.getCount().intValue()
));
}

/**
* 여러 모임의 완료된 미팅 수를 Map으로 조회
*/
private Map<Long, Integer> getMeetingCountMap(List<Long> gatheringIds) {
if (gatheringIds.isEmpty()) {
return Map.of();
}
return meetingRepository.countByGatheringIdsAndStatus(gatheringIds, MeetingStatus.DONE)
.stream()
.collect(Collectors.toMap(
GatheringCountProjection::getGatheringId,
p -> p.getCount().intValue()
));
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/dokdok/meeting/repository/MeetingRepository.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.dokdok.meeting.repository;

import com.dokdok.gathering.repository.GatheringCountProjection;
import com.dokdok.meeting.entity.Meeting;
import com.dokdok.meeting.entity.MeetingStatus;
import org.springframework.data.domain.Page;
Expand Down Expand Up @@ -113,4 +114,14 @@ Optional<Meeting> findTopByGatheringIdAndBookIdAndMeetingStatusOrderByMeetingSta
""")
List<Meeting> findByIdInWithGathering(@Param("meetingIds") List<Long> meetingIds);

/**
* 여러 모임의 완료된 미팅 수를 한번에 조회
*/
@Query("SELECT m.gathering.id AS gatheringId, COUNT (m) AS count " +
"FROM Meeting m " +
"WHERE m.gathering.id IN :gatheringIds " +
"AND m.meetingStatus = :status " +
"GROUP BY m.gathering.id")
List<GatheringCountProjection> countByGatheringIdsAndStatus(@Param("gatheringIds") List<Long> gatheringIds, @Param("status") MeetingStatus status);

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.dokdok.gathering.entity.GatheringStatus;
import com.dokdok.gathering.exception.GatheringErrorCode;
import com.dokdok.gathering.exception.GatheringException;
import com.dokdok.gathering.repository.GatheringCountProjection;
import com.dokdok.gathering.repository.GatheringMemberRepository;
import com.dokdok.gathering.repository.GatheringRepository;
import com.dokdok.gathering.util.InvitationCodeGenerator;
Expand Down Expand Up @@ -308,10 +309,16 @@ void getFavoriteGatherings_Success() {
List<GatheringMember> favoriteMembers = List.of(member1, member2);

given(gatheringMemberRepository.findFavoriteGatheringsByUserId(userId)).willReturn(favoriteMembers);
given(gatheringMemberRepository.countActiveMembersByStatus(1L)).willReturn(1);
given(gatheringMemberRepository.countActiveMembersByStatus(2L)).willReturn(1);
given(meetingRepository.countByGatheringIdAndMeetingStatus(1L, MeetingStatus.DONE)).willReturn(3);
given(meetingRepository.countByGatheringIdAndMeetingStatus(2L, MeetingStatus.DONE)).willReturn(5);
given(gatheringMemberRepository.countActiveMembersByGatherings(List.of(1L, 2L)))
.willReturn(List.of(
createCountProjection(1L, 1L),
createCountProjection(2L, 1L)
));
given(meetingRepository.countByGatheringIdsAndStatus(List.of(1L, 2L), MeetingStatus.DONE))
.willReturn(List.of(
createCountProjection(1L, 3L),
createCountProjection(2L, 5L)
));

// when
FavoriteGatheringListResponse response = gatheringService.getFavoriteGatherings();
Expand Down Expand Up @@ -341,10 +348,17 @@ void getFavoriteGatherings_Success() {

securityUtilMock.verify(SecurityUtil::getCurrentUserId, times(1));
verify(gatheringMemberRepository, times(1)).findFavoriteGatheringsByUserId(eq(userId));
verify(gatheringMemberRepository, times(1)).countActiveMembersByStatus(1L);
verify(gatheringMemberRepository, times(1)).countActiveMembersByStatus(2L);
verify(meetingRepository, times(1)).countByGatheringIdAndMeetingStatus(1L, MeetingStatus.DONE);
verify(meetingRepository, times(1)).countByGatheringIdAndMeetingStatus(2L, MeetingStatus.DONE);
verify(gatheringMemberRepository, times(1)).countActiveMembersByGatherings(List.of(1L, 2L));
verify(meetingRepository, times(1)).countByGatheringIdsAndStatus(List.of(1L, 2L), MeetingStatus.DONE);
}

private GatheringCountProjection createCountProjection(Long gatheringId, Long count) {
return new GatheringCountProjection() {
@Override
public Long getGatheringId() { return gatheringId; }
@Override
public Long getCount() { return count; }
};
}

@Test
Expand All @@ -366,8 +380,8 @@ void getFavoriteGatherings_EmptyList() {

securityUtilMock.verify(SecurityUtil::getCurrentUserId, times(1));
verify(gatheringMemberRepository, times(1)).findFavoriteGatheringsByUserId(eq(userId));
verify(gatheringMemberRepository, times(0)).countActiveMembersByStatus(any());
verify(meetingRepository, times(0)).countByGatheringIdAndMeetingStatus(any(), any());
verify(gatheringMemberRepository, times(0)).countActiveMembersByGatherings(any());
verify(meetingRepository, times(0)).countByGatheringIdsAndStatus(any(), any());
}

@Test
Expand Down Expand Up @@ -401,10 +415,17 @@ void getMyGatherings_Success_FirstPage() {

given(gatheringMemberRepository.findMyGatheringsFirstPage(eq(userId), any(Pageable.class)))
.willReturn(members);
given(gatheringMemberRepository.countActiveMembersByStatus(1L)).willReturn(1);
given(gatheringMemberRepository.countActiveMembersByStatus(2L)).willReturn(1);
given(meetingRepository.countByGatheringIdAndMeetingStatus(1L, MeetingStatus.DONE)).willReturn(3);
given(meetingRepository.countByGatheringIdAndMeetingStatus(2L, MeetingStatus.DONE)).willReturn(5);
given(gatheringMemberRepository.countMyGatherings(userId)).willReturn(2);
given(gatheringMemberRepository.countActiveMembersByGatherings(List.of(1L, 2L)))
.willReturn(List.of(
createCountProjection(1L, 1L),
createCountProjection(2L, 1L)
));
given(meetingRepository.countByGatheringIdsAndStatus(List.of(1L, 2L), MeetingStatus.DONE))
.willReturn(List.of(
createCountProjection(1L, 3L),
createCountProjection(2L, 5L)
));

// when
CursorResponse<GatheringListItemResponse, MyGatheringCursor> response =
Expand All @@ -418,6 +439,8 @@ void getMyGatherings_Success_FirstPage() {
assertThat(response.nextCursor()).isNull();

verify(gatheringMemberRepository).findMyGatheringsFirstPage(eq(userId), any(Pageable.class));
verify(gatheringMemberRepository).countActiveMembersByGatherings(List.of(1L, 2L));
verify(meetingRepository).countByGatheringIdsAndStatus(List.of(1L, 2L), MeetingStatus.DONE);
}

@Test
Expand Down Expand Up @@ -452,8 +475,12 @@ void getMyGatherings_Success_HasNextPage() {

given(gatheringMemberRepository.findMyGatheringsFirstPage(eq(userId), any(Pageable.class)))
.willReturn(members);
given(gatheringMemberRepository.countActiveMembersByStatus(any())).willReturn(1);
given(meetingRepository.countByGatheringIdAndMeetingStatus(any(), eq(MeetingStatus.DONE))).willReturn(0);
given(gatheringMemberRepository.countMyGatherings(userId)).willReturn(2);
// pageSize=1이므로 pageMembers는 member1만 포함, gatheringIds = List.of(1L)
given(gatheringMemberRepository.countActiveMembersByGatherings(List.of(1L)))
.willReturn(List.of(createCountProjection(1L, 1L)));
given(meetingRepository.countByGatheringIdsAndStatus(List.of(1L), MeetingStatus.DONE))
.willReturn(List.of(createCountProjection(1L, 0L)));

// when
CursorResponse<GatheringListItemResponse, MyGatheringCursor> response =
Expand Down