Skip to content
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)
Copy link
Collaborator

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가 적용되지 않을 것 같습니다!

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);

Comment on lines +14 to +16
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: 해당 쿼리는 게시물의 "첫 번째" 사진만을 가져와야 하는데, 현재 쿼리는 해당 로직이 없는 것 같습니다.

}
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: 마찬가지로 @BatchSize가 적용되지 않아 불필요한 코드로 보입니다.

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);
Comment on lines +15 to +16
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분 구현이 가장 예상했던 형태에 가까운 것 같습니다.

}

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);
Comment on lines +17 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: 현재 Entity 분리를 감안했을 때 해당 부분이 의도대로 동작할지 의문입니다.


}
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)
Copy link
Collaborator

@airoca airoca Mar 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: 이렇게 되면 실질적으로 (photoRepository의) findFirstPhotosByPostIds는 첫 번째 사진을 가져오는 메서드가 아니게 되는 것 같습니다. 메서드명에 혼돈이 생기고, 불필요한 조회도 발생할 것 같아요.

.stream()
.map(PhotoMapper::toDomain)
.toList();

return photos.stream()
.collect(Collectors.toMap(
photo -> photo.getPost().getPostId(),
photo -> photo,
(existing, replacement) -> existing // 중복 방지
Copy link
Collaborator

Choose a reason for hiding this comment

The 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)
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: 이렇게 사용되지 않는 import는 최종 커밋 전에 깔끔하게 청소해주면 더 좋을 것 같아요!


@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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: 개별 게시물이 여러 카테고리를 갖도록 변경된다는 점이 반영되면 좋을 것 같습니다.

}
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: 결과적으로는 의도대로 동작할 것 같지만, 위의 피드백처럼 postRepositoryfindFirstPhotosByPostIds 쿼리 자체에서 첫 번째 사진만 가져오도록 하는 수정 고려해주세요!

Map<Long, PostCategory> postCategories = postPort.findPostCategoriesByPostIds(postIds);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 조회된 결과 사용
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

왜 주석이 "Batch 조회된 결과 사용" 인가요?

PostCategory postCategory = postCategories.get(post.getPostId());
Copy link
Collaborator

Choose a reason for hiding this comment

The 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(),
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