-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACTOR]Zzim리스트 조회 시, User,Place,Photo,PostCategory 관련 N+1 문제 해결 #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,17 @@ | ||
| package com.spoony.spoony_server.adapter.out.persistence.post.db; | ||
|
|
||
| import org.springframework.data.jpa.repository.EntityGraph; | ||
| 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; | ||
| import java.util.Optional; | ||
|
|
||
| public interface PhotoRepository extends JpaRepository<PhotoEntity, Long> { | ||
| Optional<List<PhotoEntity>> findByPost_PostId(Long postId); | ||
|
|
||
| @Query("SELECT p FROM PhotoEntity p WHERE p.post.postId IN :postIds GROUP BY p.post.postId") | ||
| List<PhotoEntity> findFirstPhotosByPostIds(@Param("postIds") List<Long> postIds); | ||
|
|
||
|
Comment on lines
+14
to
+16
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: 해당 쿼리는 게시물의 "첫 번째" 사진만을 가져와야 하는데, 현재 쿼리는 해당 로직이 없는 것 같습니다. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,11 +5,13 @@ | |
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import org.hibernate.annotations.BatchSize; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Table(name = "post_category") | ||
| @BatchSize(size = 10) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: 마찬가지로 |
||
| public class PostCategoryEntity { | ||
|
|
||
| @Id | ||
|
|
@@ -31,4 +33,5 @@ public PostCategoryEntity(Long postCategoryId, PostEntity post, CategoryEntity c | |
| this.post = post; | ||
| this.category = category; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,18 @@ | ||
| package com.spoony.spoony_server.adapter.out.persistence.post.db; | ||
|
|
||
| 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.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| @Repository | ||
| public interface PostCategoryRepository extends JpaRepository<PostCategoryEntity, Long> { | ||
| Optional<PostCategoryEntity> findByPost_PostId(Long postID); | ||
|
|
||
| @Query("SELECT pc FROM PostCategoryEntity pc WHERE pc.post.postId IN :postIds") | ||
| List<PostCategoryEntity> findPostCategoriesByPostIds(@Param("postIds") List<Long> postIds); | ||
|
Comment on lines
+15
to
+16
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 부분 구현이 가장 예상했던 형태에 가까운 것 같습니다. |
||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,21 @@ | ||
| package com.spoony.spoony_server.adapter.out.persistence.post.db; | ||
|
|
||
| import org.springframework.data.jpa.repository.EntityGraph; | ||
| 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.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| @Repository | ||
| public interface PostRepository extends JpaRepository<PostEntity, Long> { | ||
|
|
||
| List<PostEntity> findByUser_UserId(Long userId); | ||
|
|
||
| @EntityGraph(attributePaths = {"photos", "postCategories", "postCategories.category"}) | ||
| @Query("SELECT p FROM PostEntity p WHERE p.postId = :postId") | ||
| Optional<PostEntity> findPostWithPhotosAndCategories(@Param("postId") Long postId); | ||
|
Comment on lines
+17
to
+19
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: 현재 Entity 분리를 감안했을 때 해당 부분이 의도대로 동작할지 의문입니다. |
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,8 @@ | |
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
|
|
@@ -39,7 +41,20 @@ public Long countZzimByPostId(Long postId) { | |
| public boolean existsByUserIdAndPostId(Long userId, Long postId) { | ||
| return zzimPostRepository.existsByUser_UserIdAndPost_PostId(userId, postId); | ||
| } | ||
| @Override | ||
| public Map<Long, Photo> findFirstPhotosByPostIds(List<Long> postIds) { | ||
| List<Photo> photos = photoRepository.findFirstPhotosByPostIds(postIds) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: 이렇게 되면 실질적으로 ( |
||
| .stream() | ||
| .map(PhotoMapper::toDomain) | ||
| .toList(); | ||
|
|
||
| return photos.stream() | ||
| .collect(Collectors.toMap( | ||
| photo -> photo.getPost().getPostId(), | ||
| photo -> photo, | ||
| (existing, replacement) -> existing // 중복 방지 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: 해당 부분에서 게시물 당 사진 1개만 남기고 있긴 하지만, 조회 메서드 자체를 수정하는게 나아보입니다. |
||
| )); | ||
| } | ||
| @Override | ||
| public List<ZzimPost> findUserByUserId(Long userId) { | ||
| return zzimPostRepository.findByUser_UserId(userId) | ||
|
|
@@ -63,13 +78,15 @@ public void saveZzimPost(User user, Post post) { | |
| zzimPostRepository.save(zzimPostEntity); | ||
| } | ||
|
|
||
| // postId별로 개별적인 조회가 발생하여 N+1 문제 발생! | ||
| @Override | ||
| public Photo findFistPhotoById(Long postId) { | ||
| return photoRepository.findByPost_PostId(postId) | ||
| .map(list -> PhotoMapper.toDomain(list.get(0))) // 첫 번째 요소만 매핑 | ||
| .orElseThrow(() -> new BusinessException(PostErrorMessage.PHOTO_NOT_FOUND)); | ||
| } | ||
|
|
||
|
|
||
| @Override | ||
| public List<Photo> findPhotoListById(Long postId) { | ||
| return photoRepository.findByPost_PostId(postId) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import org.hibernate.annotations.BatchSize; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: 이렇게 사용되지 않는 import는 최종 커밋 전에 깔끔하게 청소해주면 더 좋을 것 같아요! |
||
|
|
||
| @Entity | ||
| @Getter | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,9 +7,11 @@ | |
| import com.spoony.spoony_server.domain.user.User; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public interface PostPort { | ||
| List<Post> findUserByUserId(Long userId); | ||
| Post findPostWithPhotosAndCategoriesByPostId(Long postId); | ||
| boolean existsByUserIdAndPostId(Long userId, Long postId); | ||
| Post findPostById(Long postId); | ||
| List<Photo> findPhotoById(Long postId); | ||
|
|
@@ -19,4 +21,5 @@ public interface PostPort { | |
| void saveMenu(Menu menu); | ||
| void savePhoto(Photo photo); | ||
| void saveScoopPost(User user, Post post); | ||
| Map<Long, PostCategory> findPostCategoriesByPostIds(List<Long> postIds); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: 개별 게시물이 여러 카테고리를 갖도록 변경된다는 점이 반영되면 좋을 것 같습니다. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,7 +52,6 @@ public void addZzimPost(ZzimAddCommand command) { | |
| zzimPostPort.saveZzimPost(user,post); | ||
| } | ||
|
|
||
| //사용자 지도 리스트 조회 | ||
| public ZzimCardListResponseDTO getZzimCardList(ZzimGetCardCommand command) { | ||
| List<ZzimPost> zzimPostList = zzimPostPort.findUserByUserId(command.getUserId()); | ||
|
|
||
|
|
@@ -70,12 +69,20 @@ public ZzimCardListResponseDTO getZzimCardList(ZzimGetCardCommand command) { | |
| } | ||
| } | ||
|
|
||
| // 🔥 N+1 문제 해결: 한 번에 모든 postId의 첫 번째 사진을 조회 | ||
| List<Long> postIds = uniquePlacePostMap.values().stream() | ||
| .map(zzimPost -> zzimPost.getPost().getPostId()) | ||
| .toList(); | ||
|
|
||
| Map<Long, Photo> firstPhotos = zzimPostPort.findFirstPhotosByPostIds(postIds); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: 결과적으로는 의도대로 동작할 것 같지만, 위의 피드백처럼 |
||
| Map<Long, PostCategory> postCategories = postPort.findPostCategoriesByPostIds(postIds); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: 같은 코멘트를 여러 번 남기고 있지만, 카테고리 복수 선택 가능! 꼭 기억해주세요. |
||
| List<ZzimCardResponseDTO> zzimCardResponses = uniquePlacePostMap.values().stream() | ||
| .map(zzimPost -> { | ||
| Post post = zzimPost.getPost(); | ||
| Place place = post.getPlace(); | ||
| Photo photo = zzimPostPort.findFistPhotoById(post.getPostId()); | ||
| PostCategory postCategory = postCategoryPort.findPostCategoryByPostId(post.getPostId()); | ||
| Photo photo = firstPhotos.get(post.getPostId()); // 🔥 Batch 조회된 결과 사용 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 왜 주석이 "Batch 조회된 결과 사용" 인가요? |
||
| PostCategory postCategory = postCategories.get(post.getPostId()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: 이 부분도 카테고리 복수 선택 가능 고려해야 할 겁니다. |
||
| //PostCategory postCategory = postCategoryPort.findPostCategoryByPostId(post.getPostId()); | ||
|
|
||
| CategoryColorResponseDTO categoryColorResponse = new CategoryColorResponseDTO( | ||
| postCategory.getCategory().getCategoryId(), | ||
|
|
@@ -84,13 +91,12 @@ public ZzimCardListResponseDTO getZzimCardList(ZzimGetCardCommand command) { | |
| postCategory.getCategory().getTextColor(), | ||
| postCategory.getCategory().getBackgroundColor()); | ||
|
|
||
|
|
||
| return new ZzimCardResponseDTO( | ||
| place.getPlaceId(), // placeId 추가 | ||
| place.getPlaceId(), | ||
| place.getPlaceName(), | ||
| place.getPlaceAddress(), | ||
| post.getTitle(), | ||
| photo.getPhotoUrl(), | ||
| photo != null ? photo.getPhotoUrl() : null, // 사진이 없을 경우 null 처리 | ||
| place.getLatitude(), | ||
| place.getLongitude(), | ||
| categoryColorResponse | ||
|
|
@@ -101,6 +107,56 @@ public ZzimCardListResponseDTO getZzimCardList(ZzimGetCardCommand command) { | |
| return new ZzimCardListResponseDTO(zzimCardResponses.size(), zzimCardResponses); | ||
| } | ||
|
|
||
|
|
||
| // //사용자 지도 리스트 조회 | ||
| // public ZzimCardListResponseDTO getZzimCardList(ZzimGetCardCommand command) { | ||
| // List<ZzimPost> zzimPostList = zzimPostPort.findUserByUserId(command.getUserId()); | ||
| // | ||
| // Map<Long, ZzimPost> uniquePlacePostMap = new LinkedHashMap<>(); | ||
| // | ||
| // for (ZzimPost zzimPost : zzimPostList) { | ||
| // Place place = zzimPost.getPost().getPlace(); | ||
| // if (place == null) { | ||
| // throw new BusinessException(PlaceErrorMessage.PLACE_NOT_FOUND); | ||
| // } | ||
| // | ||
| // Long placeId = place.getPlaceId(); | ||
| // if (!uniquePlacePostMap.containsKey(placeId)) { | ||
| // uniquePlacePostMap.put(placeId, zzimPost); | ||
| // } | ||
| // } | ||
| // | ||
| // List<ZzimCardResponseDTO> zzimCardResponses = uniquePlacePostMap.values().stream() | ||
| // .map(zzimPost -> { | ||
| // Post post = zzimPost.getPost(); | ||
| // Place place = post.getPlace(); | ||
| // Photo photo = zzimPostPort.findFistPhotoById(post.getPostId()); | ||
| // PostCategory postCategory = postCategoryPort.findPostCategoryByPostId(post.getPostId()); | ||
| // | ||
| // CategoryColorResponseDTO categoryColorResponse = new CategoryColorResponseDTO( | ||
| // postCategory.getCategory().getCategoryId(), | ||
| // postCategory.getCategory().getCategoryName(), | ||
| // postCategory.getCategory().getIconUrlColor(), | ||
| // postCategory.getCategory().getTextColor(), | ||
| // postCategory.getCategory().getBackgroundColor()); | ||
| // | ||
| // | ||
| // return new ZzimCardResponseDTO( | ||
| // place.getPlaceId(), // placeId 추가 | ||
| // place.getPlaceName(), | ||
| // place.getPlaceAddress(), | ||
| // post.getTitle(), | ||
| // photo.getPhotoUrl(), | ||
| // place.getLatitude(), | ||
| // place.getLongitude(), | ||
| // categoryColorResponse | ||
| // ); | ||
| // }) | ||
| // .collect(Collectors.toList()); | ||
| // | ||
| // return new ZzimCardListResponseDTO(zzimCardResponses.size(), zzimCardResponses); | ||
| // } | ||
|
|
||
| public ZzimFocusListResponseDTO getZzimFocusList(ZzimGetFocusCommand command) { | ||
| User user = userPort.findUserById(command.getUserId()); | ||
| List<ZzimPost> zzimPostList = zzimPostPort.findUserByUserId(user.getUserId()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Post와 Photo가 1:N 관계라서
@BatchSize가 적용되지 않을 것 같습니다!