Skip to content
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

[REFACTOR]Zzim리스트 조회 시, User,Place,Photo,PostCategory 관련 N+1 문제 해결 #115

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
import com.spoony.spoony_server.global.message.business.PostErrorMessage;
import com.spoony.spoony_server.global.message.business.UserErrorMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Adapter
Expand All @@ -48,6 +48,11 @@ public List<Post> findUserByUserId(Long userId) {
.map(PostMapper::toDomain)
.collect(Collectors.toList());
}
public Post findPostWithPhotosAndCategoriesByPostId(Long postId) {
return postRepository.findPostWithPhotosAndCategories(postId)
.map(PostMapper::toDomain)
.orElseThrow(() -> new BusinessException(PostErrorMessage.POST_NOT_FOUND));
}

public Post findPostById(Long postId) {
return postRepository.findById(postId)
Expand All @@ -60,6 +65,18 @@ public PostCategory findPostCategoryByPostId(Long postId) {
.map(PostCategoryMapper::toDomain)
.orElseThrow(() -> new BusinessException(CategoryErrorMessage.CATEGORY_NOT_FOUND));
}
@Override
public Map<Long, PostCategory> findPostCategoriesByPostIds(List<Long> postIds) {
List<PostCategoryEntity> postCategoryEntities = postCategoryRepository.findPostCategoriesByPostIds(postIds);

return postCategoryEntities.stream()
.map(PostCategoryMapper::toDomain) // PostCategoryEntity -> PostCategory 변환
.collect(Collectors.toMap(
postCategory -> postCategory.getPost().getPostId(),
postCategory -> postCategory,
(existing, replacement) -> existing // 중복 방지
));
}

public Category findCategoryById(Long categoryId) {
return categoryRepository.findById(categoryId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "category")

public class CategoryEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "photo")
@BatchSize(size = 10)
public class PhotoEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
public class PostCategoryEntity {

@Id
Expand All @@ -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);
}

Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.BatchSize;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

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

@Entity
@Getter
Expand Down Expand Up @@ -41,6 +43,8 @@ public class PostEntity {
@LastModifiedDate
private LocalDateTime updatedAt;



@Builder
public PostEntity(Long postId, UserEntity user, PlaceEntity place, String title, String description) {
this.postId = postId;
Expand Down
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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Repository
@RequiredArgsConstructor
Expand All @@ -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)
.stream()
.map(PhotoMapper::toDomain)
.toList();

return photos.stream()
.collect(Collectors.toMap(
photo -> photo.getPost().getPostId(),
photo -> photo,
(existing, replacement) -> existing // 중복 방지
));
}
@Override
public List<ZzimPost> findUserByUserId(Long userId) {
return zzimPostRepository.findByUser_UserId(userId)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.BatchSize;

@Entity
@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
public interface ZzimPostRepository extends JpaRepository<ZzimPostEntity, Long> {
Long countByPost_PostId(Long postId);
boolean existsByUser_UserIdAndPost_PostId(Long userId, Long postId);


List<ZzimPostEntity> findByUser_UserId(Long userId);
void deleteByUser_UserIdAndPost_PostId(Long userId, Long postId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.spoony.spoony_server.domain.zzim.ZzimPost;

import java.util.List;
import java.util.Map;

public interface ZzimPostPort {
Long countZzimByPostId(Long postId);
Expand All @@ -15,4 +16,5 @@ public interface ZzimPostPort {
List<ZzimPost> findUserByUserId(Long userId);
void saveZzimPost(User user, Post post);
void deleteByUserAndPost(User user, Post post);
Map<Long, Photo> findFirstPhotosByPostIds(List<Long> postIds); // 🔥 추가된 메서드
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ public class PostService implements
@Transactional
public PostResponseDTO getPostById(PostGetCommand command) {

Post post = postPort.findPostById(command.getPostId());
//Post post = postPort.findPostById(command.getPostId());
Post post = postPort.findPostWithPhotosAndCategoriesByPostId(command.getPostId());

User user = userPort.findUserById(command.getUserId());

PostCategory postCategory = postCategoryPort.findPostCategoryByPostId(post.getPostId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -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);
Map<Long, PostCategory> postCategories = postPort.findPostCategoriesByPostIds(postIds);
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 조회된 결과 사용
PostCategory postCategory = postCategories.get(post.getPostId());
//PostCategory postCategory = postCategoryPort.findPostCategoryByPostId(post.getPostId());

CategoryColorResponseDTO categoryColorResponse = new CategoryColorResponseDTO(
postCategory.getCategory().getCategoryId(),
Expand All @@ -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
Expand All @@ -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());
Expand Down