Skip to content

Commit

Permalink
[REFACTOR] 채팅 목록 조회 로직 개선 (#169)
Browse files Browse the repository at this point in the history
* feat: 커서 기반으로 채팅 내역 조회하는 커스텀 리파지토리 구현

* fix: 매칭 조회 쿼리에 fetch join 적용

* refactor: DTO 변환 메서드를 정적 팩토리 메서드로 구현

* refactor: 매칭 서비스 채팅 내역 조회 메서드 개선

- 첫 조회 시에만 알림 모두 읽기 적용해 부하 줄임
- 정적 팩토리 메서드로 코드 간략화
- fetch join으로 프록시 객체 초기화 오류 해결
- QueryDSL의 이점을 살린 커스텀 리파지토리의 단일 메서드로 로직 간략화

* refactor: 불필요한 메서드 제거

* style: 컨벤션 개선
  • Loading branch information
Profile-exe authored Sep 27, 2024
1 parent 4c96d02 commit 495b4c2
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import econo.buddybridge.matching.dto.ReceiverDto;
import econo.buddybridge.post.entity.PostType;
import lombok.Builder;

import java.util.List;
import lombok.Builder;

@Builder
public record ChatMessageCustomPage(
Expand All @@ -15,4 +14,5 @@ public record ChatMessageCustomPage(
Long cursor,
Boolean nextPage
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public record ChatMessageReqDto(
String content,
MessageType messageType
) {

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package econo.buddybridge.chat.chatmessage.dto;

import com.querydsl.core.annotations.QueryProjection;
import econo.buddybridge.chat.chatmessage.entity.MessageType;
import lombok.Builder;

import java.time.LocalDateTime;
import lombok.Builder;

@Builder
public record ChatMessageResDto(
Expand All @@ -13,4 +13,8 @@ public record ChatMessageResDto(
MessageType messageType,
LocalDateTime createdAt
) {

@QueryProjection
public ChatMessageResDto {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package econo.buddybridge.chat.chatmessage.dto;

import java.util.List;

public record ChatMessagesWithCursor(
List<ChatMessageResDto> chatMessages,
Long cursor,
boolean nextPage
) {

}
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
package econo.buddybridge.chat.chatmessage.repository;

import econo.buddybridge.chat.chatmessage.entity.ChatMessage;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface ChatMessageRepository extends JpaRepository<ChatMessage,Long> {
public interface ChatMessageRepository extends JpaRepository<ChatMessage, Long>, ChatMessageRepositoryCustom {

@Query("SELECT cm FROM ChatMessage cm WHERE cm.matching.id = :matchingId ORDER BY cm.id DESC")
List<ChatMessage> findLastMessageByMatchingId(Long matchingId, Pageable pageable);

@Query("SELECT cm FROM ChatMessage cm WHERE cm.matching.id = :matchingId ORDER BY cm.id DESC")
Slice<ChatMessage> findByMatchingId(Long matchingId, Pageable pageable);

@Query("SELECT cm FROM ChatMessage cm WHERE cm.matching.id = :matchingId AND cm.id < :cursor ORDER BY cm.id DESC")
Slice<ChatMessage> findByMatchingIdAndIdGreaterThan(Long matchingId, Long cursor, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package econo.buddybridge.chat.chatmessage.repository;

import econo.buddybridge.chat.chatmessage.dto.ChatMessagesWithCursor;
import econo.buddybridge.matching.entity.Matching;
import org.springframework.data.domain.Pageable;

public interface ChatMessageRepositoryCustom {

ChatMessagesWithCursor findByMatching(Matching matching, Long cursor, Pageable page);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package econo.buddybridge.chat.chatmessage.repository;

import static econo.buddybridge.chat.chatmessage.entity.QChatMessage.chatMessage;

import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import econo.buddybridge.chat.chatmessage.dto.ChatMessageResDto;
import econo.buddybridge.chat.chatmessage.dto.ChatMessagesWithCursor;
import econo.buddybridge.chat.chatmessage.dto.QChatMessageResDto;
import econo.buddybridge.matching.entity.Matching;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;

@RequiredArgsConstructor
public class ChatMessageRepositoryImpl implements ChatMessageRepositoryCustom {

private final JPAQueryFactory queryFactory;

@Override
public ChatMessagesWithCursor findByMatching(Matching matching, Long cursor, Pageable page) {
int pageSize = page.getPageSize();

List<ChatMessageResDto> content = queryFactory
.select(new QChatMessageResDto(
chatMessage.id,
chatMessage.sender.id,
chatMessage.content,
chatMessage.messageType,
chatMessage.createdAt
))
.from(chatMessage)
.where(chatMessage.matching.eq(matching), buildCursorPredicate(cursor))
.orderBy(chatMessage.id.desc())
.limit(pageSize + 1)
.fetch();

boolean nextPage = false;
if (content.size() > pageSize) {
content.removeLast();
nextPage = true;
}

Long nextCursor = nextPage ? content.getLast().messageId() : -1L;

return new ChatMessagesWithCursor(content, nextCursor, nextPage);
}

private Predicate buildCursorPredicate(Long cursor) {
if (cursor == null) {
return null;
}

return chatMessage.id.lt(cursor);
}
}
8 changes: 6 additions & 2 deletions src/main/java/econo/buddybridge/matching/dto/ReceiverDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ public record ReceiverDto(
public ReceiverDto {
}

public ReceiverDto(Member receiver) {
this(receiver.getId(), receiver.getName(), receiver.getProfileImageUrl());
public static ReceiverDto from(Member receiver) {
return ReceiverDto.builder()
.receiverId(receiver.getId())
.receiverName(receiver.getName())
.receiverProfileImg(receiver.getProfileImageUrl())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package econo.buddybridge.matching.repository;

import econo.buddybridge.matching.entity.Matching;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import org.springframework.data.jpa.repository.Query;

public interface MatchingRepository extends JpaRepository<Matching, Long> {
List<Matching> findByPostId(Long postId);

@Query("SELECT m FROM Matching m JOIN FETCH m.post JOIN FETCH m.giver JOIN FETCH m.taker WHERE m.id = :matchingId")
Optional<Matching> findByIdWithMembersAndPost(Long matchingId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import econo.buddybridge.chat.chatmessage.dto.ChatMessageCustomPage;
import econo.buddybridge.chat.chatmessage.dto.ChatMessageResDto;
import econo.buddybridge.chat.chatmessage.entity.ChatMessage;
import econo.buddybridge.chat.chatmessage.dto.ChatMessagesWithCursor;
import econo.buddybridge.chat.chatmessage.repository.ChatMessageRepository;
import econo.buddybridge.matching.dto.MatchingCustomPage;
import econo.buddybridge.matching.dto.ReceiverDto;
Expand All @@ -18,8 +18,6 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -42,49 +40,27 @@ public MatchingCustomPage getMatchings(Long memberId, Integer size, LocalDateTim
@Transactional // 메시지 조회
public ChatMessageCustomPage getMatchingRoomMessages(Long memberId, Long matchingId, Integer size, Long cursor) {

// 사용자 확인 // TODO: 예외처리 필요, 사용자가 매칭방에 속해있지 않을 경우 500 발생
Matching matching = matchingService.findMatchingByIdOrThrow(matchingId);
Matching matching = matchingService.findByIdWithMembersAndPost(matchingId);

if (!matching.getGiver().getId().equals(memberId) && !matching.getTaker().getId().equals(memberId)) {
throw MatchingUnauthorizedAccessException.EXCEPTION;
}

Pageable pageable = PageRequest.of(0, size + 1);
Slice<ChatMessage> chatMessagesSlice;

if (cursor == null) {
chatMessagesSlice = chatMessageRepository.findByMatchingId(matchingId, pageable);
} else {
chatMessagesSlice = chatMessageRepository.findByMatchingIdAndIdGreaterThan(matchingId, cursor, pageable);
if (cursor == null) { // 첫 조회 시에 알림 읽음 처리
notificationService.markAsReadByMatchingRoom(memberId, matchingId); // 해당 매칭방의 알림을 읽음 처리
}

notificationService.markAsReadByMatchingRoom(memberId, matchingId); // 해당 매칭방의 알림을 읽음 처리

Post post = matching.getPost();

Member receiver = getReceiver(matching, memberId);
ReceiverDto receiverDto = ReceiverDto.from(receiver);

ReceiverDto receiverDto = ReceiverDto.builder()
.receiverId(receiver.getId())
.receiverName(receiver.getName())
.receiverProfileImg(receiver.getProfileImageUrl())
.build();

List<ChatMessage> chatMessageList = chatMessagesSlice.getContent();
ChatMessagesWithCursor chatMessagesWithCursor = chatMessageRepository.findByMatching(matching, cursor, PageRequest.of(0, size));

List<ChatMessageResDto> chatMessageResDtoList = chatMessageList.stream().limit(size)
.map(chatMessage -> ChatMessageResDto.builder()
.messageId(chatMessage.getId())
.senderId(chatMessage.getSender().getId())
.content(chatMessage.getContent())
.messageType(chatMessage.getMessageType())
.createdAt(chatMessage.getCreatedAt())
.build()).toList();
List<ChatMessageResDto> chatMessageResDtos = chatMessagesWithCursor.chatMessages();
Long nextCursor = chatMessagesWithCursor.cursor();
boolean nextPage = chatMessagesWithCursor.nextPage();

boolean nextPage = chatMessageList.size() > size;

Long nextCursor = nextPage ? chatMessageResDtoList.isEmpty() ? -1L : chatMessageResDtoList.getLast().messageId() : -1L;
return new ChatMessageCustomPage(post.getPostType(), post.getId(), receiverDto, chatMessageResDtoList, nextCursor, nextPage);
Post post = matching.getPost();
return new ChatMessageCustomPage(post.getPostType(), post.getId(), receiverDto, chatMessageResDtos, nextCursor, nextPage);
}

private Member getReceiver(Matching matching, Long memberId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public Matching findMatchingByIdOrThrow(Long matchingId) {
.orElseThrow(() -> MatchingNotFoundException.EXCEPTION);
}

@Transactional(readOnly = true)
public Matching findByIdWithMembersAndPost(Long matchingId) {
return matchingRepository.findByIdWithMembersAndPost(matchingId)
.orElseThrow(() -> MatchingNotFoundException.EXCEPTION);
}

@Transactional
public Long createMatchingById(MatchingReqDto matchingReqDto, Long memberId) {
Post post = postService.findPostByIdOrThrow(matchingReqDto.postId());
Expand Down

0 comments on commit 495b4c2

Please sign in to comment.