diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatRoomData.java b/src/main/java/com/example/solidconnection/chat/dto/ChatRoomData.java new file mode 100644 index 000000000..491df930a --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatRoomData.java @@ -0,0 +1,24 @@ +package com.example.solidconnection.chat.dto; + +import com.example.solidconnection.chat.domain.ChatMessage; +import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public record ChatRoomData( + Map latestMessages, + Map unreadCounts, + Map partnerUsers +) { + + public static ChatRoomData from(List latestMessages, + List unreadCounts, + List partnerUsers) { + return new ChatRoomData( + latestMessages.stream().collect(Collectors.toMap(msg -> msg.getChatRoom().getId(), msg -> msg)), + unreadCounts.stream().collect(Collectors.toMap(UnreadCountDto::chatRoomId, UnreadCountDto::count)), + partnerUsers.stream().collect(Collectors.toMap(SiteUser::getId, user -> user)) + ); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/dto/UnreadCountDto.java b/src/main/java/com/example/solidconnection/chat/dto/UnreadCountDto.java new file mode 100644 index 000000000..a6fd9fc77 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/dto/UnreadCountDto.java @@ -0,0 +1,8 @@ +package com.example.solidconnection.chat.dto; + +public record UnreadCountDto( + long chatRoomId, + long count +) { + +} diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.java index ad0f15630..e27e3e86d 100644 --- a/src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.java +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.java @@ -1,7 +1,8 @@ package com.example.solidconnection.chat.repository; import com.example.solidconnection.chat.domain.ChatMessage; -import java.util.Optional; +import com.example.solidconnection.chat.dto.UnreadCountDto; +import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; @@ -18,5 +19,33 @@ public interface ChatMessageRepository extends JpaRepository """) Slice findByRoomIdWithPaging(@Param("roomId") long roomId, Pageable pageable); - Optional findFirstByChatRoomIdOrderByCreatedAtDesc(long chatRoomId); + @Query(""" + SELECT cm FROM ChatMessage cm + WHERE cm.id IN ( + SELECT MAX(cm2.id) + FROM ChatMessage cm2 + WHERE cm2.chatRoom.id IN :chatRoomIds + GROUP BY cm2.chatRoom.id + ) + """) + List findLatestMessagesByChatRoomIds(@Param("chatRoomIds") List chatRoomIds); + + @Query(""" + SELECT new com.example.solidconnection.chat.dto.UnreadCountDto( + cm.chatRoom.id, + COUNT(cm) + ) + FROM ChatMessage cm + LEFT JOIN ChatReadStatus crs ON crs.chatRoomId = cm.chatRoom.id + AND crs.chatParticipantId = ( + SELECT cp.id FROM ChatParticipant cp + WHERE cp.chatRoom.id = cm.chatRoom.id + AND cp.siteUserId = :userId + ) + WHERE cm.chatRoom.id IN :chatRoomIds + AND cm.senderId != :userId + AND (crs.updatedAt IS NULL OR cm.createdAt > crs.updatedAt) + GROUP BY cm.chatRoom.id + """) + List countUnreadMessagesBatch(@Param("chatRoomIds") List chatRoomIds, @Param("userId") long userId); } diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java index ad815dbe1..d5630f53a 100644 --- a/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java @@ -9,30 +9,21 @@ public interface ChatRoomRepository extends JpaRepository { @Query(""" - SELECT cr FROM ChatRoom cr - JOIN cr.chatParticipants cp - WHERE cp.siteUserId = :userId AND cr.isGroup = false + SELECT DISTINCT cr FROM ChatRoom cr + JOIN FETCH cr.chatParticipants + WHERE cr.id IN ( + SELECT DISTINCT cp2.chatRoom.id + FROM ChatParticipant cp2 + WHERE cp2.siteUserId = :userId + ) + AND cr.isGroup = false ORDER BY ( SELECT MAX(cm.createdAt) FROM ChatMessage cm WHERE cm.chatRoom = cr ) DESC NULLS LAST """) - List findOneOnOneChatRoomsByUserId(@Param("userId") long userId); - - @Query(""" - SELECT COUNT(cm) FROM ChatMessage cm - LEFT JOIN ChatReadStatus crs ON crs.chatRoomId = cm.chatRoom.id - AND crs.chatParticipantId = ( - SELECT cp.id FROM ChatParticipant cp - WHERE cp.chatRoom.id = :chatRoomId - AND cp.siteUserId = :userId - ) - WHERE cm.chatRoom.id = :chatRoomId - AND cm.senderId != :userId - AND (crs.updatedAt IS NULL OR cm.createdAt > crs.updatedAt) - """) - long countUnreadMessages(@Param("chatRoomId") long chatRoomId, @Param("userId") long userId); + List findOneOnOneChatRoomsByUserIdWithParticipants(@Param("userId") long userId); boolean existsByMentoringId(long mentoringId); } 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 4a9c02eed..615c83dfb 100644 --- a/src/main/java/com/example/solidconnection/chat/service/ChatService.java +++ b/src/main/java/com/example/solidconnection/chat/service/ChatService.java @@ -21,11 +21,11 @@ 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.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import java.time.ZonedDateTime; +import java.util.Collections; import java.util.List; -import java.util.Optional; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -60,28 +60,50 @@ public ChatService(ChatRoomRepository chatRoomRepository, @Transactional(readOnly = true) public ChatRoomListResponse getChatRooms(long siteUserId) { - // todo : n + 1 문제 해결 필요! - List chatRooms = chatRoomRepository.findOneOnOneChatRoomsByUserId(siteUserId); - List chatRoomInfos = chatRooms.stream() - .map(chatRoom -> toChatRoomResponse(chatRoom, siteUserId)) + List chatRooms = chatRoomRepository.findOneOnOneChatRoomsByUserIdWithParticipants(siteUserId); + + if (chatRooms.isEmpty()) { + return ChatRoomListResponse.of(Collections.emptyList()); + } + + ChatRoomData chatRoomData = getChatRoomData(chatRooms, siteUserId); + + List responses = chatRooms.stream() + .map(chatRoom -> createChatRoomResponse(chatRoom, siteUserId, chatRoomData)) .toList(); - return ChatRoomListResponse.of(chatRoomInfos); + + return ChatRoomListResponse.of(responses); } - private ChatRoomResponse toChatRoomResponse(ChatRoom chatRoom, long siteUserId) { - Optional latestMessage = chatMessageRepository.findFirstByChatRoomIdOrderByCreatedAtDesc(chatRoom.getId()); - String lastChatMessage = latestMessage.map(ChatMessage::getContent).orElse(""); - ZonedDateTime lastReceivedTime = latestMessage.map(ChatMessage::getCreatedAt).orElse(null); + private ChatRoomData getChatRoomData(List chatRooms, long siteUserId) { + List chatRoomIds = chatRooms.stream().map(ChatRoom::getId).toList(); + List partnerUserIds = chatRooms.stream() + .map(chatRoom -> findPartner(chatRoom, siteUserId).getSiteUserId()) + .toList(); - ChatParticipant partnerParticipant = findPartner(chatRoom, siteUserId); + return ChatRoomData.from( + chatMessageRepository.findLatestMessagesByChatRoomIds(chatRoomIds), + chatMessageRepository.countUnreadMessagesBatch(chatRoomIds, siteUserId), + siteUserRepository.findAllByIdIn(partnerUserIds) + ); + } - SiteUser siteUser = siteUserRepository.findById(partnerParticipant.getSiteUserId()) - .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - ChatParticipantResponse partner = ChatParticipantResponse.of(siteUser.getId(), siteUser.getNickname(), siteUser.getProfileImageUrl()); + private ChatRoomResponse createChatRoomResponse(ChatRoom chatRoom, long siteUserId, ChatRoomData chatRoomData) { + ChatMessage latestMessage = chatRoomData.latestMessages().get(chatRoom.getId()); + ChatParticipant partner = findPartner(chatRoom, siteUserId); + SiteUser partnerUser = chatRoomData.partnerUsers().get(partner.getSiteUserId()); - long unReadCount = chatRoomRepository.countUnreadMessages(chatRoom.getId(), siteUserId); + if (partnerUser == null) { + throw new CustomException(USER_NOT_FOUND); + } - return ChatRoomResponse.of(chatRoom.getId(), lastChatMessage, lastReceivedTime, partner, unReadCount); + return ChatRoomResponse.of( + chatRoom.getId(), + latestMessage != null ? latestMessage.getContent() : "", + latestMessage != null ? latestMessage.getCreatedAt() : null, + ChatParticipantResponse.of(partnerUser.getId(), partnerUser.getNickname(), partnerUser.getProfileImageUrl()), + chatRoomData.unreadCounts().getOrDefault(chatRoom.getId(), 0L) + ); } private ChatParticipant findPartner(ChatRoom chatRoom, long siteUserId) { diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java index 864e72ed3..73422ba9f 100644 --- a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java +++ b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java @@ -19,4 +19,6 @@ public interface SiteUserRepository extends JpaRepository { @Query("SELECT u FROM SiteUser u WHERE u.quitedAt <= :cutoffDate") List findUsersToBeRemoved(@Param("cutoffDate") LocalDate cutoffDate); + + List findAllByIdIn(List ids); }