diff --git a/src/main/java/until/the/eternity/auctionhistory/infrastructure/persistence/AuctionHistoryQueryDslRepository.java b/src/main/java/until/the/eternity/auctionhistory/infrastructure/persistence/AuctionHistoryQueryDslRepository.java index d5a86889..e26045ba 100644 --- a/src/main/java/until/the/eternity/auctionhistory/infrastructure/persistence/AuctionHistoryQueryDslRepository.java +++ b/src/main/java/until/the/eternity/auctionhistory/infrastructure/persistence/AuctionHistoryQueryDslRepository.java @@ -120,7 +120,11 @@ private BooleanBuilder buildHistoryPredicate( builder.and(ah.itemSubCategory.eq(c.itemSubCategory())); } if (c.itemName() != null && !c.itemName().isBlank()) { - builder.and(ah.itemName.containsIgnoreCase(c.itemName())); + if (Boolean.TRUE.equals(c.isExactItemName())) { + builder.and(ah.itemName.eq(c.itemName())); + } else { + builder.and(ah.itemName.containsIgnoreCase(c.itemName())); + } } // 가격 조건 (PriceSearchRequest가 있으면) diff --git a/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/AuctionHistorySearchRequest.java b/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/AuctionHistorySearchRequest.java index f68928f7..72b2bb7f 100644 --- a/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/AuctionHistorySearchRequest.java +++ b/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/AuctionHistorySearchRequest.java @@ -6,6 +6,12 @@ @Schema(description = "경매 거래내역 검색 조건") public record AuctionHistorySearchRequest( @Schema(description = "아이템 이름 (like 검색)", example = "페러시우스 타이탄 블레이드") String itemName, + @Schema( + description = "아이템 이름 완전 일치 검색 여부 (true: eq, false: like)", + requiredMode = Schema.RequiredMode.NOT_REQUIRED, + defaultValue = "false", + example = "false") + Boolean isExactItemName, @Schema(description = "대분류 카테고리", example = "근거리 장비") String itemTopCategory, @Schema(description = "소분류 카테고리", example = "검") String itemSubCategory, @Schema(description = "거래 일자 조건") DateAuctionBuyRequest dateAuctionBuyRequest, diff --git a/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/DateAuctionBuyRequest.java b/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/DateAuctionBuyRequest.java index 715a807c..892b61d8 100644 --- a/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/DateAuctionBuyRequest.java +++ b/src/main/java/until/the/eternity/auctionhistory/interfaces/rest/dto/request/DateAuctionBuyRequest.java @@ -4,5 +4,5 @@ @Schema(description = "거래 일자 조건") public record DateAuctionBuyRequest( - @Schema(description = "거래 일자 시작 범위", example = "2025-01-01") String dateAuctionBuyFrom, - @Schema(description = "거래 일자 종료 범위", example = "2025-12-31") String dateAuctionBuyTo) {} + @Schema(description = "거래 일자 시작 범위", example = "2026-02-01") String dateAuctionBuyFrom, + @Schema(description = "거래 일자 종료 범위", example = "2026-02-11") String dateAuctionBuyTo) {} diff --git a/src/main/java/until/the/eternity/auctionrealtime/infrastructure/persistence/AuctionRealtimeQueryDslRepository.java b/src/main/java/until/the/eternity/auctionrealtime/infrastructure/persistence/AuctionRealtimeQueryDslRepository.java index 84bb5aa4..6df0b688 100644 --- a/src/main/java/until/the/eternity/auctionrealtime/infrastructure/persistence/AuctionRealtimeQueryDslRepository.java +++ b/src/main/java/until/the/eternity/auctionrealtime/infrastructure/persistence/AuctionRealtimeQueryDslRepository.java @@ -109,7 +109,11 @@ private BooleanBuilder buildItemPredicate( builder.and(ar.itemSubCategory.eq(c.itemSubCategory())); } if (c.itemName() != null && !c.itemName().isBlank()) { - builder.and(ar.itemName.containsIgnoreCase(c.itemName())); + if (Boolean.TRUE.equals(c.isExactItemName())) { + builder.and(ar.itemName.eq(c.itemName())); + } else { + builder.and(ar.itemName.containsIgnoreCase(c.itemName())); + } } if (c.priceSearchRequest() != null) { diff --git a/src/main/java/until/the/eternity/auctionrealtime/interfaces/rest/dto/request/AuctionRealtimeSearchRequest.java b/src/main/java/until/the/eternity/auctionrealtime/interfaces/rest/dto/request/AuctionRealtimeSearchRequest.java index 19d06834..dc8396dc 100644 --- a/src/main/java/until/the/eternity/auctionrealtime/interfaces/rest/dto/request/AuctionRealtimeSearchRequest.java +++ b/src/main/java/until/the/eternity/auctionrealtime/interfaces/rest/dto/request/AuctionRealtimeSearchRequest.java @@ -8,6 +8,12 @@ @Schema(description = "실시간 경매장 검색 조건") public record AuctionRealtimeSearchRequest( @Schema(description = "아이템 이름 (like 검색)", example = "페러시우스 타이탄 블레이드") String itemName, + @Schema( + description = "아이템 이름 완전 일치 검색 여부 (true: eq, false: like)", + requiredMode = Schema.RequiredMode.NOT_REQUIRED, + defaultValue = "false", + example = "false") + Boolean isExactItemName, @Schema(description = "대분류 카테고리", example = "근거리 장비") String itemTopCategory, @Schema(description = "소분류 카테고리", example = "검") String itemSubCategory, @Schema(description = "가격 검색 조건") PriceSearchRequest priceSearchRequest, diff --git a/src/main/resources/db/migration/V20__optimize_auction_history_search_indexes.sql b/src/main/resources/db/migration/V20__optimize_auction_history_search_indexes.sql new file mode 100644 index 00000000..c8f15040 --- /dev/null +++ b/src/main/resources/db/migration/V20__optimize_auction_history_search_indexes.sql @@ -0,0 +1,17 @@ +-- Optimize auction history search indexes for fixed filters: +-- item_top_category, item_sub_category, date_auction_buy + +-- Replace legacy index that does not align with date-range + date sort pattern +DROP INDEX idx_top_sub_item ON auction_history; + +-- Main search index: +-- Equality filters first, range/sort column last +-- date_auction_buy는 where 조건에 무조건 포함 및 높은 확률로 정렬 조건 +CREATE INDEX idx_ah_top_sub_name_date + ON auction_history (item_top_category, item_sub_category, item_name, date_auction_buy DESC); + +-- Item option subquery index: +-- Supports option_type filtering + grouping by auction_history_id +-- 옵션 검색 서브 쿼리를 위한 인덱스 +CREATE INDEX idx_ahio_type_history + ON auction_history_item_option (option_type, auction_history_id); \ No newline at end of file diff --git a/src/test/java/until/the/eternity/auctionhistory/application/service/AuctionHistoryServiceTest.java b/src/test/java/until/the/eternity/auctionhistory/application/service/AuctionHistoryServiceTest.java index 7ae6dd98..243269c3 100644 --- a/src/test/java/until/the/eternity/auctionhistory/application/service/AuctionHistoryServiceTest.java +++ b/src/test/java/until/the/eternity/auctionhistory/application/service/AuctionHistoryServiceTest.java @@ -43,7 +43,7 @@ class AuctionHistoryServiceTest { void search_should_return_paged_response() { // given AuctionHistorySearchRequest searchRequest = - new AuctionHistorySearchRequest(null, null, null, null, null, null); + new AuctionHistorySearchRequest(null, null, null, null, null, null, null); PageRequestDto pageRequestDto = mock(PageRequestDto.class); Pageable pageable = PageRequest.of(0, 10); when(pageRequestDto.toPageable()).thenReturn(pageable); diff --git a/src/test/java/until/the/eternity/iteminfo/application/service/ItemInfoServiceTest.java b/src/test/java/until/the/eternity/iteminfo/application/service/ItemInfoServiceTest.java index 57644486..0c472953 100644 --- a/src/test/java/until/the/eternity/iteminfo/application/service/ItemInfoServiceTest.java +++ b/src/test/java/until/the/eternity/iteminfo/application/service/ItemInfoServiceTest.java @@ -121,7 +121,8 @@ void findAllDetail_should_allow_null_topCategory() { ItemInfoSearchRequest searchRequest = new ItemInfoSearchRequest(null, null, null); Pageable pageable = PageRequest.of(0, 20); Page emptyPage = new PageImpl<>(List.of(), pageable, 0); - when(itemInfoRepository.searchWithPagination(searchRequest, pageable)).thenReturn(emptyPage); + when(itemInfoRepository.searchWithPagination(searchRequest, pageable)) + .thenReturn(emptyPage); // when Page result = itemInfoService.findAllDetail(searchRequest, pageable); @@ -138,7 +139,8 @@ void findAllDetail_should_allow_blank_topCategory() { ItemInfoSearchRequest searchRequest = new ItemInfoSearchRequest(null, null, ""); Pageable pageable = PageRequest.of(0, 20); Page emptyPage = new PageImpl<>(List.of(), pageable, 0); - when(itemInfoRepository.searchWithPagination(searchRequest, pageable)).thenReturn(emptyPage); + when(itemInfoRepository.searchWithPagination(searchRequest, pageable)) + .thenReturn(emptyPage); // when Page result = itemInfoService.findAllDetail(searchRequest, pageable); @@ -200,7 +202,8 @@ void findAllDetail_should_return_filtered_items_with_all_conditions() { void findAllSummary_should_allow_null_topCategory() { // given ItemInfoSearchRequest searchRequest = new ItemInfoSearchRequest(null, null, null); - when(itemInfoRepository.search(eq(searchRequest), any(Pageable.class))).thenReturn(List.of()); + when(itemInfoRepository.search(eq(searchRequest), any(Pageable.class))) + .thenReturn(List.of()); // when List result = @@ -332,7 +335,8 @@ void findBySubCategory_should_return_empty_list_when_no_results() { void findAllSummary_should_allow_blank_topCategory() { // given ItemInfoSearchRequest searchRequest = new ItemInfoSearchRequest(null, null, ""); - when(itemInfoRepository.search(eq(searchRequest), any(Pageable.class))).thenReturn(List.of()); + when(itemInfoRepository.search(eq(searchRequest), any(Pageable.class))) + .thenReturn(List.of()); // when List result =