diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java b/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java index 170a93f05..aa7369451 100644 --- a/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java @@ -28,7 +28,7 @@ public class ChatMessage extends BaseEntity { @Column(nullable = false, length = 500) private String content; - private long senderId; + private long senderId; // chat_participant의 id @ManyToOne(fetch = FetchType.LAZY) private ChatRoom chatRoom; diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatMessageResponse.java b/src/main/java/com/example/solidconnection/chat/dto/ChatMessageResponse.java index a3728b7fd..b9551b9b2 100644 --- a/src/main/java/com/example/solidconnection/chat/dto/ChatMessageResponse.java +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatMessageResponse.java @@ -6,7 +6,7 @@ public record ChatMessageResponse( long id, String content, - long senderId, + long senderId, // siteUserId ZonedDateTime createdAt, List attachments ) { diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatParticipantResponse.java b/src/main/java/com/example/solidconnection/chat/dto/ChatParticipantResponse.java index ffa6b9b8c..18276b561 100644 --- a/src/main/java/com/example/solidconnection/chat/dto/ChatParticipantResponse.java +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatParticipantResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.chat.dto; public record ChatParticipantResponse( - long partnerId, + long partnerId, // siteUserId String nickname, String profileUrl ) { diff --git a/src/main/java/com/example/solidconnection/chat/service/ChatService.java b/src/main/java/com/example/solidconnection/chat/service/ChatService.java index 78530d752..57f8cad65 100644 --- a/src/main/java/com/example/solidconnection/chat/service/ChatService.java +++ b/src/main/java/com/example/solidconnection/chat/service/ChatService.java @@ -15,6 +15,7 @@ import com.example.solidconnection.chat.dto.ChatMessageSendRequest; import com.example.solidconnection.chat.dto.ChatMessageSendResponse; import com.example.solidconnection.chat.dto.ChatParticipantResponse; +import com.example.solidconnection.chat.dto.ChatRoomData; import com.example.solidconnection.chat.dto.ChatRoomListResponse; import com.example.solidconnection.chat.dto.ChatRoomResponse; import com.example.solidconnection.chat.repository.ChatMessageRepository; @@ -23,11 +24,15 @@ import com.example.solidconnection.chat.repository.ChatRoomRepository; import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.chat.dto.ChatRoomData; +import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -43,6 +48,7 @@ public class ChatService { private final ChatParticipantRepository chatParticipantRepository; private final ChatReadStatusRepository chatReadStatusRepository; private final SiteUserRepository siteUserRepository; + private final MentorRepository mentorRepository; private final SimpMessageSendingOperations simpMessageSendingOperations; @@ -51,12 +57,14 @@ public ChatService(ChatRoomRepository chatRoomRepository, ChatParticipantRepository chatParticipantRepository, ChatReadStatusRepository chatReadStatusRepository, SiteUserRepository siteUserRepository, + MentorRepository mentorRepository, @Lazy SimpMessageSendingOperations simpMessageSendingOperations) { this.chatRoomRepository = chatRoomRepository; this.chatMessageRepository = chatMessageRepository; this.chatParticipantRepository = chatParticipantRepository; this.chatReadStatusRepository = chatReadStatusRepository; this.siteUserRepository = siteUserRepository; + this.mentorRepository = mentorRepository; this.simpMessageSendingOperations = simpMessageSendingOperations; } @@ -114,13 +122,38 @@ public SliceResponse getChatMessages(long siteUserId, long Slice chatMessages = chatMessageRepository.findByRoomIdWithPaging(roomId, pageable); - List content = chatMessages.getContent().stream() - .map(this::toChatMessageResponse) - .toList(); + Map participantIdToParticipant = buildParticipantIdToParticipantMap(chatMessages); + List content = buildChatMessageResponses(chatMessages, participantIdToParticipant); return SliceResponse.of(content, chatMessages); } + // senderId(chatParticipantId)로 chatParticipant 맵 생성 + private Map buildParticipantIdToParticipantMap(Slice chatMessages) { + Set participantIds = chatMessages.getContent().stream() + .map(ChatMessage::getSenderId) + .collect(Collectors.toSet()); + + return chatParticipantRepository.findAllById(participantIds).stream() + .collect(Collectors.toMap(ChatParticipant::getId, Function.identity())); + } + + private List buildChatMessageResponses( + Slice chatMessages, + Map participantIdToParticipant + ) { + return chatMessages.getContent().stream() + .map(message -> { + ChatParticipant senderParticipant = participantIdToParticipant.get(message.getSenderId()); + if (senderParticipant == null) { + throw new CustomException(CHAT_PARTICIPANT_NOT_FOUND); + } + long externalSenderId = senderParticipant.getSiteUserId(); + return toChatMessageResponse(message, externalSenderId); + }) + .toList(); + } + @Transactional(readOnly = true) public ChatParticipantResponse getChatPartner(long siteUserId, Long roomId) { ChatRoom chatRoom = chatRoomRepository.findById(roomId) @@ -128,6 +161,7 @@ public ChatParticipantResponse getChatPartner(long siteUserId, Long roomId) { ChatParticipant partnerParticipant = findPartner(chatRoom, siteUserId); SiteUser siteUser = siteUserRepository.findById(partnerParticipant.getSiteUserId()) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + return ChatParticipantResponse.of(siteUser.getId(), siteUser.getNickname(), siteUser.getProfileImageUrl()); } @@ -148,7 +182,7 @@ public void validateChatRoomParticipant(long siteUserId, long roomId) { } } - private ChatMessageResponse toChatMessageResponse(ChatMessage message) { + private ChatMessageResponse toChatMessageResponse(ChatMessage message, long externalSenderId) { List attachments = message.getChatAttachments().stream() .map(attachment -> ChatAttachmentResponse.of( attachment.getId(), @@ -162,7 +196,7 @@ private ChatMessageResponse toChatMessageResponse(ChatMessage message) { return ChatMessageResponse.of( message.getId(), message.getContent(), - message.getSenderId(), + externalSenderId, message.getCreatedAt(), attachments ); diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java index 8a41fba84..46791d071 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java @@ -6,7 +6,7 @@ import java.time.ZonedDateTime; public record MentoringForMentorResponse( - long mentoringId, + Long roomId, String profileImageUrl, String nickname, boolean isChecked, @@ -14,9 +14,9 @@ public record MentoringForMentorResponse( ZonedDateTime createdAt ) { - public static MentoringForMentorResponse of(Mentoring mentoring, SiteUser partner) { + public static MentoringForMentorResponse of(Mentoring mentoring, SiteUser partner, Long roomId) { return new MentoringForMentorResponse( - mentoring.getId(), + roomId, partner.getProfileImageUrl(), partner.getNickname(), mentoring.getCheckedAtByMentor() != null, diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java index 430602f1e..85dbbc0bf 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java @@ -2,7 +2,9 @@ import com.example.solidconnection.location.region.domain.Region; import com.example.solidconnection.mentor.domain.Mentor; +import java.util.List; import java.util.Optional; +import java.util.Set; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; @@ -23,4 +25,6 @@ public interface MentorRepository extends JpaRepository { WHERE u.region = :region """) Slice findAllByRegion(@Param("region") Region region, Pageable pageable); + + List findAllBySiteUserIdIn(Set siteUserIds); } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java index 57753f983..606990068 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java @@ -118,17 +118,6 @@ public SliceResponse getMentoringsForMentee( return SliceResponse.of(content, mentoringSlice); } - // N+1 을 해결하면서 멘토링의 채팅방 정보 조회 - private Map mapMentoringIdToChatRoomIdWithBatchQuery(List mentorings) { - List mentoringIds = mentorings.stream() - .map(Mentoring::getId) - .distinct() - .toList(); - List chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds); - return chatRooms.stream() - .collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId)); - } - @Transactional(readOnly = true) public SliceResponse getMentoringsForMentor(long siteUserId, Pageable pageable) { Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) @@ -140,9 +129,15 @@ public SliceResponse getMentoringsForMentor(long sit Mentoring::getMenteeId ); + Map mentoringIdToChatRoomId = mapMentoringIdToChatRoomIdWithBatchQuery(mentoringSlice.getContent()); + List content = new ArrayList<>(); for (Mentoring mentoring : mentoringSlice) { - content.add(MentoringForMentorResponse.of(mentoring, mentoringToPartnerUser.get(mentoring))); + content.add(MentoringForMentorResponse.of( + mentoring, + mentoringToPartnerUser.get(mentoring), + mentoringIdToChatRoomId.get(mentoring.getId()) + )); } return SliceResponse.of(content, mentoringSlice); @@ -165,4 +160,15 @@ private Map mapMentoringToPartnerUserWithBatchQuery( mentoring -> partnerIdToPartnerUsermap.get(getPartnerId.apply(mentoring)) )); } + + // N+1 을 해결하면서 멘토링의 채팅방 정보 조회 + private Map mapMentoringIdToChatRoomIdWithBatchQuery(List mentorings) { + List mentoringIds = mentorings.stream() + .map(Mentoring::getId) + .distinct() + .toList(); + List chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds); + return chatRooms.stream() + .collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId)); + } } diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java index cb680b986..959d8e491 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java @@ -97,15 +97,18 @@ class 멘토의_멘토링_목록_조회_테스트 { Mentoring mentoring2 = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser2.getId()); Mentoring mentoring3 = mentoringFixture.거절된_멘토링(mentor1.getId(), menteeUser3.getId()); + ChatRoom chatRoom2 = chatRoomFixture.멘토링_채팅방(mentoring2.getId()); + // when SliceResponse response = mentoringQueryService.getMentoringsForMentor(mentorUser1.getId(), pageable); // then - assertThat(response.content()).extracting(MentoringForMentorResponse::mentoringId) + assertThat(response.content()) + .extracting(MentoringForMentorResponse::verifyStatus, MentoringForMentorResponse::roomId) .containsExactlyInAnyOrder( - mentoring1.getId(), - mentoring2.getId(), - mentoring3.getId() + tuple(VerifyStatus.PENDING, null), + tuple(VerifyStatus.APPROVED, chatRoom2.getId()), + tuple(VerifyStatus.REJECTED, null) ); } @@ -137,10 +140,10 @@ class 멘토의_멘토링_목록_조회_테스트 { // then assertThat(response.content()) - .extracting(MentoringForMentorResponse::mentoringId, MentoringForMentorResponse::isChecked) + .extracting(MentoringForMentorResponse::nickname, MentoringForMentorResponse::isChecked) .containsExactlyInAnyOrder( - tuple(mentoring1.getId(), false), - tuple(mentoring2.getId(), true) + tuple(menteeUser1.getNickname(), false), + tuple(menteeUser2.getNickname(), true) ); }