Skip to content

Commit

Permalink
MATE-103 : [REFACTOR] 회원 프로필 타임라인 조회 성능 개선 (#112)
Browse files Browse the repository at this point in the history
* MATE-103 : [REFACTOR] QueryDSL을 활용하여 가져오던 참여 직관 목록, 경기 정보를 VisitRepository JPQL로 수정

* MATE-103 : [REFACTOR] 타임라인 조회 SQL 쿼리 및 성능 개선

* MATE-103 : [TEST] 타임라인 조회 서비스 테스트
  • Loading branch information
jooinjoo authored Dec 9, 2024
1 parent 585c692 commit e872b07
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 129 deletions.
17 changes: 4 additions & 13 deletions src/main/java/com/example/mate/domain/match/entity/Match.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
package com.example.mate.domain.match.entity;

import com.example.mate.domain.constant.StadiumInfo;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Entity
@Table(name = "`match`") // 테이블 이름을 backtick(`)으로 감싸서 사용
@Getter
Expand Down Expand Up @@ -48,7 +39,7 @@ public class Match {
private Integer homeScore;
private Integer awayScore;

@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name = "weather_id")
private Weather weather;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
@Repository
public interface MatchRepository extends JpaRepository<Match, Long> {
List<Match> findTop5ByOrderByMatchTimeDesc();

List<Match> findTop3ByHomeTeamIdOrAwayTeamIdOrderByMatchTimeDesc(Long homeTeamId, Long awayTeamId);

@Query("SELECT m FROM Match m " +
"WHERE (m.status = :status1 AND m.homeTeamId = :homeTeamId) " +
"OR (m.status = :status2 AND m.awayTeamId = :awayTeamId) " +
Expand All @@ -26,6 +28,7 @@ List<Match> findRecentCompletedMatches(
@Param("status2") MatchStatus status2,
@Param("awayTeamId") Long awayTeamId
);

@Query("SELECT m FROM Match m WHERE (m.homeTeamId = :teamId OR m.awayTeamId = :teamId) " +
"AND m.matchTime BETWEEN :startDate AND :endDate " +
"AND m.status = 'SCHEDULED' " +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.example.mate.domain.mate.repository;

import com.example.mate.domain.match.entity.Match;
import com.example.mate.domain.mate.entity.MatePost;
import com.example.mate.domain.mate.entity.Status;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;

@Repository
public interface MateRepository extends JpaRepository<MatePost, Long>, MateRepositoryCustom {
@Query("""
Expand All @@ -21,7 +21,4 @@ public interface MateRepository extends JpaRepository<MatePost, Long>, MateRepos
""")
List<MatePost> findMainPagePosts(@Param("teamId") Long teamId, @Param("now") LocalDateTime now,
@Param("statuses") List<Status> statuses, Pageable pageable);

@Query("SELECT m.match FROM MatePost m WHERE m.id = :matePostId")
Match findMatchByMatePostId(@Param("matePostId") Long matePostId);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package com.example.mate.domain.mate.repository;

import com.example.mate.domain.mate.entity.MateReview;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface MateReviewRepository extends JpaRepository<MateReview, Long> {

int countByRevieweeId(Long revieweeId);

Optional<MateReview> findMateReviewByVisitIdAndReviewerIdAndRevieweeId(Long visitId, Long reviewerId,
Long revieweeId);
@Query("""
SELECT mr
FROM MateReview mr
WHERE mr.visit.id = :visitId
AND mr.reviewer.id = :reviewerId
ORDER BY mr.reviewee.id ASC
""")
List<MateReview> findMateReviewsByVisitIdAndReviewerId(@Param("visitId") Long visitId,
@Param("reviewerId") Long reviewerId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface VisitPartRepository extends JpaRepository<VisitPart, VisitPartI
FROM VisitPart vp
WHERE vp.visit.id = :visitId
AND vp.member.id != :memberId
ORDER BY vp.member.id ASC
""")
List<Member> findMembersByVisitIdExcludeMember(@Param("visitId") Long visitId, @Param("memberId") Long memberId);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
package com.example.mate.domain.mate.repository;

import com.example.mate.domain.match.entity.Match;
import com.example.mate.domain.mate.entity.Visit;
import com.example.mate.domain.member.dto.response.MyTimelineResponse;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface VisitRepository extends JpaRepository<Visit, Long> {

@Query("""
SELECT new com.example.mate.domain.member.dto.response.MyTimelineResponse(v.id, v.post.id, vp.member.id)
FROM Visit v
JOIN v.participants vp
WHERE vp.member.id = :memberId
ORDER BY v.id DESC
""")
Page<MyTimelineResponse> findVisitsByMemberId(@Param("memberId") Long memberId, Pageable pageable);

@Query("""
SELECT m
FROM Visit v
JOIN v.post mp
JOIN mp.match m
JOIN v.participants vp
WHERE vp.member.id = :memberId
ORDER BY v.id DESC
""")
List<Match> findMatchesByMemberId(@Param("memberId") Long memberId);
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@
import com.example.mate.domain.goods.repository.GoodsReviewRepositoryCustom;
import com.example.mate.domain.match.entity.Match;
import com.example.mate.domain.mate.entity.MateReview;
import com.example.mate.domain.mate.repository.MateRepository;
import com.example.mate.domain.mate.repository.MateReviewRepository;
import com.example.mate.domain.mate.repository.MateReviewRepositoryCustom;
import com.example.mate.domain.mate.repository.VisitPartRepository;
import com.example.mate.domain.mate.repository.VisitRepository;
import com.example.mate.domain.member.dto.response.MyGoodsRecordResponse;
import com.example.mate.domain.member.dto.response.MyReviewResponse;
import com.example.mate.domain.member.dto.response.MyTimelineResponse;
import com.example.mate.domain.member.dto.response.MyVisitResponse;
import com.example.mate.domain.member.dto.response.MyVisitResponse.MateReviewResponse;
import com.example.mate.domain.member.entity.Member;
import com.example.mate.domain.member.repository.MemberRepository;
import com.example.mate.domain.member.repository.TimelineRepositoryCustom;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@Transactional
@RequiredArgsConstructor
Expand All @@ -39,10 +40,9 @@ public class ProfileService {
private final GoodsPostRepository goodsPostRepository;
private final MateReviewRepositoryCustom mateReviewRepositoryCustom;
private final GoodsReviewRepositoryCustom goodsReviewRepositoryCustom;
private final TimelineRepositoryCustom timelineRepositoryCustom;
private final MateRepository mateRepository;
private final VisitPartRepository visitPartRepository;
private final MateReviewRepository mateReviewRepository;
private final VisitRepository visitRepository;

// 굿즈 판매기록 페이징 조회
@Transactional(readOnly = true)
Expand Down Expand Up @@ -104,42 +104,52 @@ public PageResponse<MyReviewResponse> getGoodsReviewPage(Long memberId, Pageable
return PageResponse.from(goodsReviewPage);
}

// TODO : 쿼리가 너무 많이 나오는 문제 -> 멘토링 및 리팩토링 필요
// 직관 타임라인 페이징 조회
@Transactional(readOnly = true)
public PageResponse<MyVisitResponse> getMyVisitPage(Long memberId, Pageable pageable) {
validateMemberId(memberId);

// 회원이 참여한 직관을 페이징하여 가져오기
Page<MyTimelineResponse> visitsByIdPage = timelineRepositoryCustom.findVisitsById(memberId, pageable);
// 회원이 참여한 직관 목록을 페이지네이션
Page<MyTimelineResponse> visitsByMemberIdPage = visitRepository.findVisitsByMemberId(memberId, pageable);

// 응답 객체 생성
List<MyVisitResponse> responses = visitsByIdPage.getContent().stream()
.map(response -> createVisitResponse(response, memberId))
.collect(Collectors.toList());
// 회원이 참여한 경기 정보 리스트
List<Match> matchesByMatePostId = visitRepository.findMatchesByMemberId(memberId);

// 각각의 직관 목록과 경기 정보에 따른 응답 객체 생성 및 주입
List<MyVisitResponse> responses = new ArrayList<>();
for (int i = 0; i < visitsByMemberIdPage.getContent().size(); i++) {
MyTimelineResponse response = visitsByMemberIdPage.getContent().get(i);
Match match = matchesByMatePostId.get(i);
responses.add(createVisitResponse(response, memberId, match));
}

// 페이징 정보 처리
return createPageResponse(visitsByIdPage, responses, pageable);
// 페이지네이션
return createPageResponse(visitsByMemberIdPage, responses, pageable);
}

private MyVisitResponse createVisitResponse(MyTimelineResponse response, Long memberId) {
// 경기 정보 가져오기
Match match = mateRepository.findMatchByMatePostId(response.getMatePostId());
private MyVisitResponse createVisitResponse(MyTimelineResponse response, Long memberId, Match match) {
// 회원이 참여한 직관에서 메이트에 남긴 모든 리뷰 리스트
List<MateReview> existReviews = mateReviewRepository
.findMateReviewsByVisitIdAndReviewerId(response.getVisitId(), memberId);

// 회원 본인을 제외한 직관 참여 리스트 가져오기
// 회원 본인을 제외한 직관 참여 메이트 리스트
List<Member> mates = visitPartRepository.findMembersByVisitIdExcludeMember(response.getVisitId(), memberId);

// 각 메이트에 대한 리뷰 생성
List<MateReviewResponse> reviews = createMateReviews(response, mates, memberId);
// 각 메이트에 대한 리뷰 여부에 따른 응답 리뷰 리스트
List<MateReviewResponse> reviews = createMateReviews(response, mates, memberId, existReviews);

return MyVisitResponse.of(match, reviews, response.getMatePostId());
}

private List<MateReviewResponse> createMateReviews(MyTimelineResponse response, List<Member> mates, Long memberId) {
private List<MateReviewResponse> createMateReviews(MyTimelineResponse response, List<Member> mates,
Long memberId, List<MateReview> existReviews) {
return mates.stream()
.map(mate -> {
Optional<MateReview> mateReview = mateReviewRepository.findMateReviewByVisitIdAndReviewerIdAndRevieweeId(
response.getVisitId(), memberId, mate.getId());
Optional<MateReview> mateReview = existReviews.stream()
.filter(review -> review.getVisit().getId().equals(response.getVisitId()) &&
review.getReviewer().getId().equals(memberId) &&
review.getReviewee().getId().equals(mate.getId()))
.findFirst(); // 해당 조건에 맞는 리뷰를 찾기
return mateReview.map(MateReviewResponse::from) // 해당 mate에 대한 리뷰가 있으면 리뷰 채워서 반환
.orElseGet(() -> MateReviewResponse.from(mate)); // 리뷰가 없으면 rating, content = null
})
Expand Down
Loading

0 comments on commit e872b07

Please sign in to comment.