Skip to content
Merged
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
@@ -1,7 +1,5 @@
package until.the.eternity.auctionhistory.application.scheduler;

import java.util.*;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -16,6 +14,9 @@
import until.the.eternity.auctionhistory.interfaces.external.dto.OpenApiAuctionHistoryResponse;
import until.the.eternity.common.enums.ItemCategory;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package until.the.eternity.auctionhistory.application.service;

import jakarta.persistence.EntityManager;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
Expand All @@ -16,6 +15,8 @@
import until.the.eternity.common.request.PageRequestDto;
import until.the.eternity.common.response.PageResponseDto;

import java.util.List;

@Service
@RequiredArgsConstructor
@Slf4j
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package until.the.eternity.auctionhistory.application.service.fetcher;

import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
Expand All @@ -11,6 +9,9 @@
import until.the.eternity.auctionhistory.interfaces.external.dto.OpenApiAuctionHistoryResponse;
import until.the.eternity.common.enums.ItemCategory;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package until.the.eternity.auctionhistory.application.service.persister;

import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
Expand All @@ -11,6 +10,8 @@
import until.the.eternity.auctionhistory.interfaces.external.dto.OpenApiAuctionHistoryResponse;
import until.the.eternity.common.enums.ItemCategory;

import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Component
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package until.the.eternity.auctionhistory.domain.entity;

import jakarta.persistence.*;
import java.time.Instant;
import java.util.List;
import lombok.*;
import until.the.eternity.auctionitemoption.domain.entity.AuctionItemOption;

import java.time.Instant;
import java.util.List;

@Entity
@Table(name = "auction_history")
@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package until.the.eternity.auctionhistory.domain.event;

import java.time.LocalDateTime;
import lombok.Getter;

import java.time.LocalDateTime;

/**
* 경매장 거래 내역 저장 완료 이벤트
* AuctionHistoryScheduler가 거래 내역을 성공적으로 저장한 후 발행됩니다.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package until.the.eternity.auctionhistory.domain.mapper;

import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import until.the.eternity.auctionhistory.domain.entity.AuctionHistory;
import until.the.eternity.auctionhistory.interfaces.rest.dto.response.AuctionHistoryDetailResponse;
import until.the.eternity.auctionhistory.interfaces.rest.dto.response.ItemOptionResponse;
import until.the.eternity.auctionitemoption.domain.entity.AuctionItemOption;

import java.util.List;

/**
* AuctionHistory Entity to internal.responseDto transfer mapper class 데이터 흐름은 external.responseDto
* -> entity -> internal.responseDto 단방향으로 흐름
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package until.the.eternity.auctionhistory.domain.mapper;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import org.mapstruct.*;
import until.the.eternity.auctionhistory.domain.entity.AuctionHistory;
import until.the.eternity.auctionhistory.interfaces.external.dto.OpenApiAuctionHistoryResponse;
import until.the.eternity.common.enums.ItemCategory;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;

@Mapper(componentModel = "spring", uses = OpenApiItemOptionMapper.class)
public interface OpenApiAuctionHistoryMapper {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package until.the.eternity.auctionhistory.domain.repository;

import java.time.Instant;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import until.the.eternity.auctionhistory.domain.entity.AuctionHistory;
import until.the.eternity.auctionhistory.interfaces.rest.dto.request.AuctionHistorySearchRequest;
import until.the.eternity.common.enums.ItemCategory;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

/** 경매장 거래 내역 POJO Repository - Mock 또는 Stub 으로 대체해 단위 테스트 용이성 확보 */
public interface AuctionHistoryRepositoryPort {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package until.the.eternity.auctionhistory.domain.service;

import java.time.Instant;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import until.the.eternity.auctionhistory.domain.repository.AuctionHistoryRepositoryPort;
import until.the.eternity.auctionhistory.interfaces.external.dto.OpenApiAuctionHistoryResponse;
import until.the.eternity.common.enums.ItemCategory;

import java.time.Instant;
import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package until.the.eternity.auctionhistory.domain.service.fetcher;

import java.util.List;
import until.the.eternity.auctionhistory.interfaces.external.dto.OpenApiAuctionHistoryResponse;
import until.the.eternity.common.enums.ItemCategory;

import java.util.List;

public interface AuctionHistoryFetcherPort {
List<OpenApiAuctionHistoryResponse> fetch(ItemCategory category);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package until.the.eternity.auctionhistory.domain.service.persister;

import java.util.List;
import until.the.eternity.auctionhistory.domain.entity.AuctionHistory;
import until.the.eternity.auctionhistory.interfaces.external.dto.OpenApiAuctionHistoryResponse;
import until.the.eternity.common.enums.ItemCategory;

import java.util.List;

public interface AuctionHistoryPersisterPort {

List<AuctionHistory> filterOutExisting(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package until.the.eternity.auctionhistory.infrastructure.persistence;

import java.time.Instant;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import until.the.eternity.auctionhistory.domain.entity.AuctionHistory;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

@Repository
public interface AuctionHistoryJpaRepository
extends JpaRepository<AuctionHistory, String>, JpaSpecificationExecutor<AuctionHistory> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
import com.querydsl.core.types.dsl.NumberTemplate;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
Expand All @@ -28,6 +23,12 @@
import until.the.eternity.auctionhistory.interfaces.rest.dto.request.PriceSearchRequest;
import until.the.eternity.auctionitemoption.domain.entity.QAuctionItemOption;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;

@Component
@RequiredArgsConstructor
class AuctionHistoryQueryDslRepository {
Expand Down Expand Up @@ -71,17 +72,32 @@ public Page<AuctionHistory> search(AuctionHistorySearchRequest condition, Pageab
// 3단계: 정렬 조건 빌드
List<OrderSpecifier<?>> orderSpecifiers = buildOrderSpecifiers(pageable, ah);

// 4단계: 모든 옵션과 함께 조회 (LEFT JOIN - 조건 없음!)
// 4단계: Deferred Join (Late Row Lookup) 패턴 적용

Copilot AI Jan 18, 2026

Copy link

Choose a reason for hiding this comment

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

The deferred join pattern optimization lacks documentation explaining why this approach is used and what performance benefits it provides. Consider adding a comment block before step 4 that explains: 1) The purpose of the two-step query (Late Row Lookup pattern), 2) The performance benefit of fetching IDs first with index-only scan, 3) Any trade-offs or limitations of this approach.

Suggested change
// 4단계: Deferred Join (Late Row Lookup) 패턴 적용
/*
* 4단계: Deferred Join (Late Row Lookup) 패턴 적용
*
* 부분은 의도적으로 "2단계 쿼리" 사용하는 Late Row Lookup 패턴입니다.
* 1) 목적:
* - 4-1단계에서 JOIN 없이 필터/정렬 조건만 적용하여 ID(기본 ) 리스트만 먼저 조회합니다.
* - 4-2단계에서 조회된 ID들을 기준으로 실제 엔티티와 연관 관계(옵션) JOIN 하여 상세 데이터를 가져옵니다.
* - , 비용이 JOIN과 넓은 row 조회를 나중으로 미루어, 먼저 "어떤 row를 볼지" 가볍게 결정합니다.
*
* 2) 성능상의 이점:
* - 4-1단계 쿼리는 auctionBuyId 기준 인덱스를 활용한 (사실상) index-only scan에 가깝게 동작할 있습니다.
* - 단계에서는 조인/대량 컬럼 로딩 없이 ID만 조회하므로, 디스크 I/O와 네트워크 전송량이 줄어듭니다.
* - 페이징(offset/limit) 가벼운 쿼리에서 처리하여, 불필요한 조인 대상 row를 읽지 않게 됩니다.
* - 4-2단계는 이미 좁혀진 ID 집합에 대해서만 JOIN을 수행하므로, 전체 쿼리 비용이 줄어드는 효과가 있습니다.
*
* 3) 트레이드오프 / 한계:
* - 쿼리가 2 실행되므로, 네트워크 round-trip이 늘어납니다.
* - ID 리스트가 매우 커지는 경우(IN 절이 비대해지는 경우)에는 DB 또는 드라이버에 추가 부담을 있습니다.
* - 정렬/필터 조건은 4-1단계와 4-2단계에서 동일하게 유지되어야 하며,
* DB 스냅샷 일관성(동시성 업데이트) 측면에서 2번의 쿼리 사이에 약간의 시차가 존재할 있습니다.
* - 따라서, 패턴은 "JOIN이 많은/행이 많은 조회"에서 튜닝 목적의 선택적 최적화로 활용해야 합니다.
*/

Copilot uses AI. Check for mistakes.
// 4-1단계: ID만 먼저 조회 (인덱스 활용)
List<String> ids =
queryFactory
.select(ah.auctionBuyId)
.from(ah)
.where(historyBuilder)
.orderBy(orderSpecifiers.toArray(new OrderSpecifier[0]))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

// 결과가 없으면 빈 페이지 반환
if (ids.isEmpty()) {
return new PageImpl<>(List.of(), pageable, 0L);
}

// 4-2단계: ID로 상세 조회 (LEFT JOIN으로 옵션 포함)
List<AuctionHistory> content =
queryFactory
.selectFrom(ah)
.leftJoin(ah.auctionItemOptions, aio)
.fetchJoin()
.where(historyBuilder)
.where(ah.auctionBuyId.in(ids))
.orderBy(orderSpecifiers.toArray(new OrderSpecifier[0]))
.distinct() // 중복 제거
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.distinct()
.fetch();
Comment on lines +92 to 101

Copilot AI Jan 18, 2026

Copy link

Choose a reason for hiding this comment

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

The deferred join pattern implementation may not preserve the original ordering when fetching the detailed results in step 4-2. After retrieving IDs in a specific order, the second query fetches by ID with the same orderBy clause, but the IN clause doesn't guarantee order preservation. The results might be returned in a different order than the IDs list. Consider either: 1) Ordering the results in-memory based on the original IDs list order, or 2) Using a positional parameter to preserve the order from the first query.

Copilot uses AI. Check for mistakes.

// Count 쿼리 (JOIN 없이 실행)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package until.the.eternity.auctionhistory.infrastructure.persistence;

import jakarta.persistence.EntityManager;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
Expand All @@ -15,6 +12,10 @@
import until.the.eternity.auctionhistory.interfaces.rest.dto.request.AuctionHistorySearchRequest;
import until.the.eternity.common.enums.ItemCategory;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

/** AuctionHistoryRepository Interface 구현체 */
@Repository
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package until.the.eternity.auctionhistory.interfaces.external.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public record OpenApiAuctionHistoryListResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import until.the.eternity.auctionitemoption.domain.dto.external.OpenApiAuctionItemOptionResponse;

import java.time.Instant;
import java.util.List;
import until.the.eternity.auctionitemoption.domain.dto.external.OpenApiAuctionItemOptionResponse;

public record OpenApiAuctionHistoryResponse(
@JsonProperty("item_name") String itemName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.v3.oas.annotations.media.Schema;

import java.util.Arrays;

/** 검색 기준 (이상/이하/같음) */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.v3.oas.annotations.media.Schema;

import java.util.Arrays;

/** 정렬 방향 (오름차순/내림차순) */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package until.the.eternity.auctionhistory.interfaces.rest.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.time.Instant;
import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package until.the.eternity.auctionitem.domain.entity;

import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.List;
import lombok.*;
import until.the.eternity.auctionitemoption.domain.entity.AuctionItemOption;

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

@Entity
@Table(name = "auction_item")
@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package until.the.eternity.auctionitemoption.domain.entity;

import jakarta.persistence.*;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import until.the.eternity.auctionhistory.domain.entity.AuctionHistory;
import until.the.eternity.auctionitem.domain.entity.AuctionItem;

import java.util.UUID;

@Entity
@Table(name = "auction_item_option")
@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -13,6 +11,9 @@
import until.the.eternity.auctionsearchoption.interfaces.rest.dto.response.FieldMetadata;
import until.the.eternity.auctionsearchoption.interfaces.rest.dto.response.SearchOptionMetadataResponse;

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

@Slf4j
@Service
@RequiredArgsConstructor
Expand Down
Loading
Loading