From 898a8cfdc8ab8ca0d5d27ffbc60f84994c3bb269 Mon Sep 17 00:00:00 2001 From: hyunji321 Date: Thu, 3 Jul 2025 11:27:51 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor=20:=20db=20connection=20=EC=A6=9D?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 10 +++++++++- src/main/resources/application.yml | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index f4a0f4a..6b01c78 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,15 @@ services: volumes: - ./db/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql - ./db/data-attractions.sql:/docker-entrypoint-initdb.d/02-data-attractions.sql - command: --default-authentication-plugin=mysql_native_password + - ./db/dummy-data-insert.sql:/docker-entrypoint-initdb.d/03-dummy-data.sql + command: + - --default-authentication-plugin=mysql_native_password + - --max_connections=500 + - --innodb_buffer_pool_size=512M + - --innodb_log_file_size=128M + + + redis: image: redis:7.2 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dedb85f..7f6397f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,6 +8,15 @@ spring: password: ${DB_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 300 # 커넥션 풀의 최대 크기 + minimum-idle: 10 # 최소 유휴 커넥션 수 + idle-timeout: 600000 # 커넥션 유휴 타임아웃(ms) (기본: 600000ms = 10분) + max-lifetime: 1800000 # 커넥션 최대 생존 시간(ms) (기본: 30분) + connection-timeout: 30000 # 커넥션 요청 대기 최대 시간(ms) + leak-detection-threshold: 30000 # 커넥션 누수 감지 시간(ms) + + ai: openai: From 2acaeca0c0449c835ddf156a848444b1729ad633 Mon Sep 17 00:00:00 2001 From: hyunji321 Date: Fri, 4 Jul 2025 18:54:48 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20N+1=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shotmap/post/application/PostService.java | 33 ++++-- .../post/presentation/PostController.java | 2 +- .../post/repository/PostRepository.java | 7 ++ .../post/response/PostImageResponse.java | 7 +- .../post/response/PostListResponse.java | 7 +- .../post/response/PostTagResponse.java | 8 ++ src/main/resources/mappers/PostMapper.xml | 106 +++++++++++++++--- 7 files changed, 143 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/shotmap/post/response/PostTagResponse.java diff --git a/src/main/java/com/shotmap/post/application/PostService.java b/src/main/java/com/shotmap/post/application/PostService.java index c5adbdd..23ef594 100644 --- a/src/main/java/com/shotmap/post/application/PostService.java +++ b/src/main/java/com/shotmap/post/application/PostService.java @@ -14,10 +14,7 @@ import com.shotmap.post.repository.TagRepository; import com.shotmap.post.request.PostCreateRequest; import com.shotmap.post.request.PostSearchNearbyRequest; -import com.shotmap.post.response.PostDetailResponse; -import com.shotmap.post.response.PostImageResponse; -import com.shotmap.post.response.PostListResponse; -import com.shotmap.post.response.TagResponse; +import com.shotmap.post.response.*; import com.shotmap.spot.application.SpotClusteringService; import com.shotmap.user.domain.User; import com.shotmap.user.response.UserResponse; @@ -32,7 +29,10 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Slf4j @Service @@ -63,10 +63,29 @@ public PagedResponse findNearbyPosts(PostSearchNearbyRequest r .pageable(pageable) .build(); + // 게시물 기본 정보 + 사용자 + 좋아요 수 List posts = postRepository.findNearbyPosts(pageRequest); - /*if(posts == null || posts.isEmpty()){ - throw new ShotmapException(PostErrorCode.NEARBY_POSTS_NOT_FOUND); - }*/ + + //게시물 id 추출 + List postIds = posts.stream() + .map(PostListResponse::getId).toList(); + + if (postIds.isEmpty()) { + return PagedResponse.of(new PageImpl<>(posts, pageable, 0)); + } + + // 게시물 이미지 조회 + Map> postImages = postRepository.selectPostImageUrlByPostIds(postIds) + .stream().collect(Collectors.groupingBy(PostImageResponse::postId)); + + //게시물 태그 조회 + Map> postTags = postRepository.selectPostTagByPostIds(postIds) + .stream().collect(Collectors.groupingBy(PostTagResponse::postId)); + + posts.forEach(post -> { + post.setImages(postImages.getOrDefault(post.getId(),List.of())); + post.setTags(postTags.getOrDefault(post.getId(),List.of())); + }); long totalItems = postRepository.countNearbyPosts(request); Page page = new PageImpl<>(posts, pageable, totalItems); diff --git a/src/main/java/com/shotmap/post/presentation/PostController.java b/src/main/java/com/shotmap/post/presentation/PostController.java index db0a770..e3535f8 100644 --- a/src/main/java/com/shotmap/post/presentation/PostController.java +++ b/src/main/java/com/shotmap/post/presentation/PostController.java @@ -26,7 +26,7 @@ public class PostController { private final PostService postService; @GetMapping("/nearby") - public ApiResponse> getNearbyPosts(PostSearchNearbyRequest request, @PageableDefault(size = 10, sort="post_create_at") Pageable pageable){ + public ApiResponse> getNearbyPosts(PostSearchNearbyRequest request, @PageableDefault(size = 10, sort="created_at") Pageable pageable){ PagedResponse response = postService.findNearbyPosts(request, pageable); return new ApiResponse<>(response); diff --git a/src/main/java/com/shotmap/post/repository/PostRepository.java b/src/main/java/com/shotmap/post/repository/PostRepository.java index 6a6daa9..f13e72b 100644 --- a/src/main/java/com/shotmap/post/repository/PostRepository.java +++ b/src/main/java/com/shotmap/post/repository/PostRepository.java @@ -6,7 +6,10 @@ import com.shotmap.post.domain.PostTag; import com.shotmap.post.domain.Tag; import com.shotmap.post.request.PostSearchNearbyRequest; +import com.shotmap.post.response.PostImageResponse; import com.shotmap.post.response.PostListResponse; +import com.shotmap.post.response.PostTagResponse; +import com.shotmap.post.response.TagResponse; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.springframework.data.domain.Pageable; @@ -21,6 +24,10 @@ public interface PostRepository { List findNearbyPosts(PageRequest pageRequest); + List selectPostImageUrlByPostIds(List postIds); + + List selectPostTagByPostIds(List postIds); + long countNearbyPosts(PostSearchNearbyRequest request); void batchUpdateSpotId(@Param("posts") List posts); diff --git a/src/main/java/com/shotmap/post/response/PostImageResponse.java b/src/main/java/com/shotmap/post/response/PostImageResponse.java index bb2f594..0f9d4c7 100644 --- a/src/main/java/com/shotmap/post/response/PostImageResponse.java +++ b/src/main/java/com/shotmap/post/response/PostImageResponse.java @@ -3,10 +3,11 @@ import com.shotmap.post.domain.PostImage; public record PostImageResponse( - Long id, + Long postId, + Long imageId, String imageUrl ) { - public PostImageResponse(PostImage image) { - this(image.getId(), image.getImageUrl()); + public PostImageResponse(PostImage postImage) { + this(postImage.getPostId(), postImage.getId(), postImage.getImageUrl()); } } diff --git a/src/main/java/com/shotmap/post/response/PostListResponse.java b/src/main/java/com/shotmap/post/response/PostListResponse.java index b0d06a6..35286b8 100644 --- a/src/main/java/com/shotmap/post/response/PostListResponse.java +++ b/src/main/java/com/shotmap/post/response/PostListResponse.java @@ -5,6 +5,7 @@ import com.shotmap.user.domain.User; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; @@ -23,8 +24,10 @@ public class PostListResponse { Long heartCount; //게시물 좋아요 수 LocalDateTime createdAt; //게시물 작성 시간 - List images; //게시물 사진 리스트 - List tags; //게시물 태그 리스틑 User user; //게시물 작성자 정보 + @Setter + List images; //게시물 사진 리스트 + @Setter + List tags; //게시물 태그 리스틑 } diff --git a/src/main/java/com/shotmap/post/response/PostTagResponse.java b/src/main/java/com/shotmap/post/response/PostTagResponse.java new file mode 100644 index 0000000..7d7baa2 --- /dev/null +++ b/src/main/java/com/shotmap/post/response/PostTagResponse.java @@ -0,0 +1,8 @@ +package com.shotmap.post.response; + +public record PostTagResponse( + Long postId, + Long tagId, + String name) +{ +} diff --git a/src/main/resources/mappers/PostMapper.xml b/src/main/resources/mappers/PostMapper.xml index e6540e4..0060732 100644 --- a/src/main/resources/mappers/PostMapper.xml +++ b/src/main/resources/mappers/PostMapper.xml @@ -11,24 +11,46 @@ AND longitude IS NOT NULL - + SELECT + p.no AS post_no, p.title, p.latitude, p.longitude, p.view_count AS post_view_count, p.created_at AS post_create_at, - hc.heart_count AS post_heart_count, + IFNULL(hc.heart_count, 0) AS post_heart_count, u.no AS user_no, u.nickname AS user_nickname, u.profile_image_url AS user_profile + FROM ( + SELECT p.no FROM post p - LEFT JOIN ( - SELECT post_no, COUNT(*) AS heart_count - FROM heart - WHERE is_deleted = FALSE - GROUP BY post_no - )hc ON p.no = hc.post_no - LEFT JOIN users u ON p.user_no = u.no WHERE p.latitude BETWEEN #{data.swLocation.latitude} AND #{data.neLocation.latitude} - AND p.longitude BETWEEN #{data.swLocation.longitude} AND #{data.neLocation.longitude} - AND p.is_deleted = false - AND u.is_deleted = false - + AND p.longitude BETWEEN #{data.swLocation.longitude} AND #{data.neLocation.longitude} + AND p.is_deleted = FALSE ORDER BY @@ -36,7 +58,63 @@ LIMIT #{pageable.pageSize} OFFSET #{pageable.offset} + ) filtered_post + JOIN post p ON p.no = filtered_post.no + LEFT JOIN ( + SELECT post_no, COUNT(*) AS heart_count + FROM heart + WHERE is_deleted = FALSE + GROUP BY post_no + ) hc ON p.no = hc.post_no + LEFT JOIN users u ON p.user_no = u.no AND u.is_deleted = FALSE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + p.no AS post_no, + p.title, + p.latitude, + p.longitude, + p.view_count AS post_view_count, + p.created_at AS post_create_at, + p.heart_count AS post_heart_count, + u.no AS user_no, + u.nickname AS user_nickname, + u.profile_image_url AS user_profile + FROM ( + SELECT p.no + FROM post p + WHERE p.latitude BETWEEN #{data.swLocation.latitude} AND #{data.neLocation.latitude} + AND p.longitude BETWEEN #{data.swLocation.longitude} AND #{data.neLocation.longitude} + AND p.is_deleted = FALSE + + ORDER BY + + ${order.property} DESC + + + LIMIT #{pageable.pageSize} OFFSET #{pageable.offset} + ) filtered_post + JOIN post p ON p.no = filtered_post.no + LEFT JOIN users u ON p.user_no = u.no AND u.is_deleted = FALSE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From a858f37506582b24f569036f7dcaa6d759f36b78 Mon Sep 17 00:00:00 2001 From: hyunji321 Date: Sun, 20 Jul 2025 18:53:06 +0900 Subject: [PATCH 5/7] =?UTF-8?q?chore:=20v2=20API=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20Security=20Config=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/shotmap/global/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/shotmap/global/config/SecurityConfig.java b/src/main/java/com/shotmap/global/config/SecurityConfig.java index 4d0d92d..23f0280 100644 --- a/src/main/java/com/shotmap/global/config/SecurityConfig.java +++ b/src/main/java/com/shotmap/global/config/SecurityConfig.java @@ -42,6 +42,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/api/v1/attractions/**").permitAll() .requestMatchers("/api/v1/spots/**").permitAll() .requestMatchers("/api/v1/posts/**").permitAll() + .requestMatchers("/api/v2/posts/**").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html", "/swagger-resources/**", "/webjars/**").permitAll() .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // CORS Preflight 허용 .anyRequest().authenticated() From 3197a61149d21bf3cee73a4956662f572c090be0 Mon Sep 17 00:00:00 2001 From: hyunji321 Date: Sun, 20 Jul 2025 18:54:55 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20v1=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shotmap/post/application/PostService.java | 35 +--- .../post/presentation/PostController.java | 2 +- .../post/repository/PostRepository.java | 11 +- src/main/resources/mappers/PostMapper.xml | 152 +++++------------- 4 files changed, 48 insertions(+), 152 deletions(-) diff --git a/src/main/java/com/shotmap/post/application/PostService.java b/src/main/java/com/shotmap/post/application/PostService.java index 23ef594..6f1a2ca 100644 --- a/src/main/java/com/shotmap/post/application/PostService.java +++ b/src/main/java/com/shotmap/post/application/PostService.java @@ -14,7 +14,10 @@ import com.shotmap.post.repository.TagRepository; import com.shotmap.post.request.PostCreateRequest; import com.shotmap.post.request.PostSearchNearbyRequest; -import com.shotmap.post.response.*; +import com.shotmap.post.response.PostDetailResponse; +import com.shotmap.post.response.PostImageResponse; +import com.shotmap.post.response.PostListResponse; +import com.shotmap.post.response.TagResponse; import com.shotmap.spot.application.SpotClusteringService; import com.shotmap.user.domain.User; import com.shotmap.user.response.UserResponse; @@ -29,10 +32,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; @Slf4j @Service @@ -63,29 +63,10 @@ public PagedResponse findNearbyPosts(PostSearchNearbyRequest r .pageable(pageable) .build(); - // 게시물 기본 정보 + 사용자 + 좋아요 수 List posts = postRepository.findNearbyPosts(pageRequest); - - //게시물 id 추출 - List postIds = posts.stream() - .map(PostListResponse::getId).toList(); - - if (postIds.isEmpty()) { - return PagedResponse.of(new PageImpl<>(posts, pageable, 0)); - } - - // 게시물 이미지 조회 - Map> postImages = postRepository.selectPostImageUrlByPostIds(postIds) - .stream().collect(Collectors.groupingBy(PostImageResponse::postId)); - - //게시물 태그 조회 - Map> postTags = postRepository.selectPostTagByPostIds(postIds) - .stream().collect(Collectors.groupingBy(PostTagResponse::postId)); - - posts.forEach(post -> { - post.setImages(postImages.getOrDefault(post.getId(),List.of())); - post.setTags(postTags.getOrDefault(post.getId(),List.of())); - }); + /*if(posts == null || posts.isEmpty()){ + throw new ShotmapException(PostErrorCode.NEARBY_POSTS_NOT_FOUND); + }*/ long totalItems = postRepository.countNearbyPosts(request); Page page = new PageImpl<>(posts, pageable, totalItems); @@ -218,4 +199,4 @@ public void cancelLikePost(Long postId, Long userId) { postRepository.deleteHeart(postId, userId); log.debug("좋아요 취소 완료 - postId={}, userId={}", postId, userId); } -} +} \ No newline at end of file diff --git a/src/main/java/com/shotmap/post/presentation/PostController.java b/src/main/java/com/shotmap/post/presentation/PostController.java index e3535f8..db0a770 100644 --- a/src/main/java/com/shotmap/post/presentation/PostController.java +++ b/src/main/java/com/shotmap/post/presentation/PostController.java @@ -26,7 +26,7 @@ public class PostController { private final PostService postService; @GetMapping("/nearby") - public ApiResponse> getNearbyPosts(PostSearchNearbyRequest request, @PageableDefault(size = 10, sort="created_at") Pageable pageable){ + public ApiResponse> getNearbyPosts(PostSearchNearbyRequest request, @PageableDefault(size = 10, sort="post_create_at") Pageable pageable){ PagedResponse response = postService.findNearbyPosts(request, pageable); return new ApiResponse<>(response); diff --git a/src/main/java/com/shotmap/post/repository/PostRepository.java b/src/main/java/com/shotmap/post/repository/PostRepository.java index f13e72b..c8b2b82 100644 --- a/src/main/java/com/shotmap/post/repository/PostRepository.java +++ b/src/main/java/com/shotmap/post/repository/PostRepository.java @@ -6,10 +6,7 @@ import com.shotmap.post.domain.PostTag; import com.shotmap.post.domain.Tag; import com.shotmap.post.request.PostSearchNearbyRequest; -import com.shotmap.post.response.PostImageResponse; import com.shotmap.post.response.PostListResponse; -import com.shotmap.post.response.PostTagResponse; -import com.shotmap.post.response.TagResponse; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.springframework.data.domain.Pageable; @@ -24,10 +21,6 @@ public interface PostRepository { List findNearbyPosts(PageRequest pageRequest); - List selectPostImageUrlByPostIds(List postIds); - - List selectPostTagByPostIds(List postIds); - long countNearbyPosts(PostSearchNearbyRequest request); void batchUpdateSpotId(@Param("posts") List posts); @@ -61,8 +54,8 @@ public interface PostRepository { void insertHeart(@Param("postId") Long postId, @Param("userId") Long userId); void deleteHeart(@Param("postId") Long postId, @Param("userId") Long userId); - + List findPostsBySpotId(@Param("spotId") Long spotId, @Param("pageable") Pageable pageable); long countPostsBySpotId(@Param("spotId") Long spotId); -} +} \ No newline at end of file diff --git a/src/main/resources/mappers/PostMapper.xml b/src/main/resources/mappers/PostMapper.xml index 0060732..0f5ce32 100644 --- a/src/main/resources/mappers/PostMapper.xml +++ b/src/main/resources/mappers/PostMapper.xml @@ -11,46 +11,24 @@ AND longitude IS NOT NULL - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + SELECT p.no AS post_no, p.title AS title, p.latitude AS latitude, p.longitude AS longitude, p.view_count AS post_view_count, p.created_at AS post_create_at, - IFNULL(hc.heart_count, 0) AS post_heart_count, + hc.heart_count AS post_heart_count, u.no AS user_no, u.nickname AS user_nickname, u.profile_image_url AS user_profile - FROM ( - SELECT p.no FROM post p + LEFT JOIN ( + SELECT post_no, COUNT(*) AS heart_count + FROM heart + WHERE is_deleted = FALSE + GROUP BY post_no + )hc ON p.no = hc.post_no + LEFT JOIN users u ON p.user_no = u.no WHERE p.latitude BETWEEN #{data.swLocation.latitude} AND #{data.neLocation.latitude} AND p.longitude BETWEEN #{data.swLocation.longitude} AND #{data.neLocation.longitude} - AND p.is_deleted = FALSE + AND p.is_deleted = false + AND u.is_deleted = false + ORDER BY @@ -58,63 +36,7 @@ LIMIT #{pageable.pageSize} OFFSET #{pageable.offset} - ) filtered_post - JOIN post p ON p.no = filtered_post.no - LEFT JOIN ( - SELECT post_no, COUNT(*) AS heart_count - FROM heart - WHERE is_deleted = FALSE - GROUP BY post_no - ) hc ON p.no = hc.post_no - LEFT JOIN users u ON p.user_no = u.no AND u.is_deleted = FALSE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -153,17 +75,17 @@ SELECT EXISTS ( - SELECT 1 - FROM heart - WHERE post_no = #{postId} - AND user_no = #{userId} - AND is_deleted = FALSE + SELECT 1 + FROM heart + WHERE post_no = #{postId} + AND user_no = #{userId} + AND is_deleted = FALSE ) @@ -348,6 +270,6 @@ UPDATE heart SET is_deleted = TRUE WHERE post_no = #{postId} - AND user_no = #{userId} + AND user_no = #{userId} \ No newline at end of file From d116035e1dd3185f24d9dda6b542f5bcbc715667 Mon Sep 17 00:00:00 2001 From: hyunji321 Date: Mon, 21 Jul 2025 01:12:43 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor=20:=20response=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?mapper=20=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/mappers/PostMapper.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/mappers/PostMapper.xml b/src/main/resources/mappers/PostMapper.xml index 0f5ce32..dfb93b3 100644 --- a/src/main/resources/mappers/PostMapper.xml +++ b/src/main/resources/mappers/PostMapper.xml @@ -176,19 +176,19 @@ - - + + - + SELECT pi.post_no AS postId, pi.no AS imageId, pi.image_url AS imageUrl FROM post_image pi WHERE pi.post_no = #{post_no} AND pi.is_deleted = FALSE - + SELECT pt.post_no AS postId, t.no AS tagId, t.name AS name FROM post_tag pt JOIN tag t ON pt.tag_no = t.no