From 2ae9c62886babed893448af527c32a04569d3098 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Sun, 24 Aug 2025 21:30:06 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20n=20+=201=20=EB=AC=B8=EC=A0=9C?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 1 + 3N + 1 -> 1 + 3 + 1로 쿼리 개선 --- .../chat/dto/UnreadCountDto.java | 8 ++ .../repository/ChatMessageRepository.java | 33 +++++++- .../chat/repository/ChatRoomRepository.java | 27 +++---- .../chat/service/ChatService.java | 75 +++++++++++++++---- .../repository/SiteUserRepository.java | 2 + 5 files changed, 110 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/chat/dto/UnreadCountDto.java 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..4332f860b 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.ChatParticipantResponse; import com.example.solidconnection.chat.dto.ChatRoomListResponse; import com.example.solidconnection.chat.dto.ChatRoomResponse; +import com.example.solidconnection.chat.dto.UnreadCountDto; import com.example.solidconnection.chat.repository.ChatMessageRepository; import com.example.solidconnection.chat.repository.ChatParticipantRepository; import com.example.solidconnection.chat.repository.ChatReadStatusRepository; @@ -25,7 +26,8 @@ import com.example.solidconnection.siteuser.repository.SiteUserRepository; import java.time.ZonedDateTime; import java.util.List; -import java.util.Optional; +import java.util.Map; +import java.util.stream.Collectors; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -60,28 +62,71 @@ 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); + List chatRoomIds = chatRooms.stream() + .map(ChatRoom::getId) .toList(); - return ChatRoomListResponse.of(chatRoomInfos); + + List latestMessages = chatMessageRepository.findLatestMessagesByChatRoomIds(chatRoomIds); + List unreadCounts = chatMessageRepository.countUnreadMessagesBatch(chatRoomIds, siteUserId); + List partnerUserIds = chatRooms.stream() + .map(chatRoom -> findPartner(chatRoom, siteUserId).getSiteUserId()) + .toList(); + List partnerUsers = siteUserRepository.findAllByIdIn(partnerUserIds); + + Map latestMessageMap = latestMessages.stream() + .collect(Collectors.toMap( + msg -> msg.getChatRoom().getId(), + msg -> msg + )); + Map unreadCountMap = unreadCounts.stream() + .collect(Collectors.toMap( + UnreadCountDto::chatRoomId, + UnreadCountDto::count + )); + Map partnerUserMap = partnerUsers.stream() + .collect(Collectors.toMap(SiteUser::getId, user -> user)); + + List chatRoomResponses = chatRooms.stream() + .map(chatRoom -> toChatRoomResponse( + chatRoom, + siteUserId, + latestMessageMap, + unreadCountMap, + partnerUserMap + )) + .toList(); + + return ChatRoomListResponse.of(chatRoomResponses); } - 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 ChatRoomResponse toChatRoomResponse( + ChatRoom chatRoom, + long siteUserId, + Map latestMessageMap, + Map unreadCountMap, + Map partnerUserMap) { + + ChatMessage latestMessage = latestMessageMap.get(chatRoom.getId()); + String lastChatMessage = latestMessage != null ? latestMessage.getContent() : ""; + ZonedDateTime lastReceivedTime = latestMessage != null ? latestMessage.getCreatedAt() : null; ChatParticipant partnerParticipant = findPartner(chatRoom, siteUserId); + SiteUser partnerUser = partnerUserMap.get(partnerParticipant.getSiteUserId()); + + if (partnerUser == null) { + throw new CustomException(USER_NOT_FOUND); + } - SiteUser siteUser = siteUserRepository.findById(partnerParticipant.getSiteUserId()) - .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - ChatParticipantResponse partner = ChatParticipantResponse.of(siteUser.getId(), siteUser.getNickname(), siteUser.getProfileImageUrl()); + ChatParticipantResponse partner = ChatParticipantResponse.of( + partnerUser.getId(), + partnerUser.getNickname(), + partnerUser.getProfileImageUrl() + ); - long unReadCount = chatRoomRepository.countUnreadMessages(chatRoom.getId(), siteUserId); + long unreadCount = unreadCountMap.getOrDefault(chatRoom.getId(), 0L); - return ChatRoomResponse.of(chatRoom.getId(), lastChatMessage, lastReceivedTime, partner, unReadCount); + return ChatRoomResponse.of(chatRoom.getId(), lastChatMessage, lastReceivedTime, partner, unreadCount); } 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); } From 62abb86a8d27055b32fed26d4c60d822b80a6bb1 Mon Sep 17 00:00:00 2001 From: Gyuhyeok99 Date: Sun, 24 Aug 2025 21:41:47 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=20=EA=B0=80?= =?UTF-8?q?=EB=8F=85=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/dto/ChatRoomData.java | 24 ++++++ .../chat/service/ChatService.java | 85 +++++++------------ 2 files changed, 55 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/chat/dto/ChatRoomData.java 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/service/ChatService.java b/src/main/java/com/example/solidconnection/chat/service/ChatService.java index 4332f860b..615c83dfb 100644 --- a/src/main/java/com/example/solidconnection/chat/service/ChatService.java +++ b/src/main/java/com/example/solidconnection/chat/service/ChatService.java @@ -15,19 +15,17 @@ import com.example.solidconnection.chat.dto.ChatParticipantResponse; import com.example.solidconnection.chat.dto.ChatRoomListResponse; import com.example.solidconnection.chat.dto.ChatRoomResponse; -import com.example.solidconnection.chat.dto.UnreadCountDto; import com.example.solidconnection.chat.repository.ChatMessageRepository; import com.example.solidconnection.chat.repository.ChatParticipantRepository; import com.example.solidconnection.chat.repository.ChatReadStatusRepository; 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.Map; -import java.util.stream.Collectors; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -63,70 +61,49 @@ public ChatService(ChatRoomRepository chatRoomRepository, @Transactional(readOnly = true) public ChatRoomListResponse getChatRooms(long siteUserId) { List chatRooms = chatRoomRepository.findOneOnOneChatRoomsByUserIdWithParticipants(siteUserId); - List chatRoomIds = chatRooms.stream() - .map(ChatRoom::getId) + + if (chatRooms.isEmpty()) { + return ChatRoomListResponse.of(Collections.emptyList()); + } + + ChatRoomData chatRoomData = getChatRoomData(chatRooms, siteUserId); + + List responses = chatRooms.stream() + .map(chatRoom -> createChatRoomResponse(chatRoom, siteUserId, chatRoomData)) .toList(); - List latestMessages = chatMessageRepository.findLatestMessagesByChatRoomIds(chatRoomIds); - List unreadCounts = chatMessageRepository.countUnreadMessagesBatch(chatRoomIds, siteUserId); + return ChatRoomListResponse.of(responses); + } + + 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(); - List partnerUsers = siteUserRepository.findAllByIdIn(partnerUserIds); - - Map latestMessageMap = latestMessages.stream() - .collect(Collectors.toMap( - msg -> msg.getChatRoom().getId(), - msg -> msg - )); - Map unreadCountMap = unreadCounts.stream() - .collect(Collectors.toMap( - UnreadCountDto::chatRoomId, - UnreadCountDto::count - )); - Map partnerUserMap = partnerUsers.stream() - .collect(Collectors.toMap(SiteUser::getId, user -> user)); - - List chatRoomResponses = chatRooms.stream() - .map(chatRoom -> toChatRoomResponse( - chatRoom, - siteUserId, - latestMessageMap, - unreadCountMap, - partnerUserMap - )) - .toList(); - return ChatRoomListResponse.of(chatRoomResponses); + return ChatRoomData.from( + chatMessageRepository.findLatestMessagesByChatRoomIds(chatRoomIds), + chatMessageRepository.countUnreadMessagesBatch(chatRoomIds, siteUserId), + siteUserRepository.findAllByIdIn(partnerUserIds) + ); } - private ChatRoomResponse toChatRoomResponse( - ChatRoom chatRoom, - long siteUserId, - Map latestMessageMap, - Map unreadCountMap, - Map partnerUserMap) { - - ChatMessage latestMessage = latestMessageMap.get(chatRoom.getId()); - String lastChatMessage = latestMessage != null ? latestMessage.getContent() : ""; - ZonedDateTime lastReceivedTime = latestMessage != null ? latestMessage.getCreatedAt() : null; - - ChatParticipant partnerParticipant = findPartner(chatRoom, siteUserId); - SiteUser partnerUser = partnerUserMap.get(partnerParticipant.getSiteUserId()); + 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()); if (partnerUser == null) { throw new CustomException(USER_NOT_FOUND); } - ChatParticipantResponse partner = ChatParticipantResponse.of( - partnerUser.getId(), - partnerUser.getNickname(), - partnerUser.getProfileImageUrl() + 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) ); - - long unreadCount = unreadCountMap.getOrDefault(chatRoom.getId(), 0L); - - return ChatRoomResponse.of(chatRoom.getId(), lastChatMessage, lastReceivedTime, partner, unreadCount); } private ChatParticipant findPartner(ChatRoom chatRoom, long siteUserId) {