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
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ dependencies {
// Spring Boot
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-aop")

// Redis
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
Expand Down
5 changes: 5 additions & 0 deletions docker-compose-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ services:
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}

# === Redis Configuration ===
REDIS_HOST: ${REDIS_HOST}
REDIS_PORT: ${REDIS_PORT}
REDIS_PASSWORD: ${REDIS_PASSWORD}

Comment on lines +35 to +39

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

여기서 REDIS_HOST/PORT/PASSWORD를 compose 환경변수로 강제 주입하면, 값이 미정의인 경우 빈 문자열로 컨테이너에 전달되어 application.yml의 기본값(${REDIS_HOST:localhost}, ${REDIS_PORT:6379})이 적용되지 않습니다. 특히 REDIS_PORT가 빈 문자열이면 Spring이 int로 바인딩하다가 실패할 수 있으니, compose 쪽에 기본값(:-6379 등)을 주거나 아예 이 환경변수 섹션을 제거하고 Spring 기본값에 맡기는 방식으로 정리해주세요.

Suggested change
# === Redis Configuration ===
REDIS_HOST: ${REDIS_HOST}
REDIS_PORT: ${REDIS_PORT}
REDIS_PASSWORD: ${REDIS_PASSWORD}

Copilot uses AI. Check for mistakes.
# === Security Configuration ===
JWT_SECRET_KEY: ${JWT_SECRET_KEY}
JWT_ACCESS_TOKEN_VALIDITY: ${JWT_ACCESS_TOKEN_VALIDITY}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import until.the.eternity.auctionhistory.application.service.AuctionHistoryCacheWarmupService;
import until.the.eternity.auctionhistory.application.service.AuctionHistoryService;
import until.the.eternity.auctionhistory.application.service.fetcher.AuctionHistoryFetcher;
import until.the.eternity.auctionhistory.application.service.persister.AuctionHistoryPersister;
Expand All @@ -27,6 +28,7 @@ public class AuctionHistoryScheduler {
private final AuctionHistoryFetcher fetcher;
private final AuctionHistoryPersister persister;
private final ApplicationEventPublisher eventPublisher;
private final AuctionHistoryCacheWarmupService cacheWarmupService;

@Value("${openapi.auction-history.delay-ms}")
private long delayMs;
Expand Down Expand Up @@ -114,12 +116,16 @@ public int fetchAndSaveAuctionHistoryAll() {
"> [SCHEDULE] AuctionHistoryScheduler saved [{}] new auction history records complete",
totalSavedCount);

// 통계 업데이트를 위한 이벤트 발행
// 통계 업데이트를 위한 이벤트 발행 (DailyStatisticsService가 일간/랭킹 캐시를 함께 무효화)
log.debug(
"> [SCHEDULE] Publishing AuctionHistorySavedEvent with {} records",
totalSavedCount);
eventPublisher.publishEvent(new AuctionHistorySavedEvent(totalSavedCount));

// 경매 거래 내역 캐시 무효화 + 역대 랭킹 캐시 무효화 + 30가지 조합 워밍업
log.info("> [SCHEDULE] Starting cache eviction and warmup");
cacheWarmupService.evictAndWarm();
Comment on lines +125 to +127

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

스케줄러 실행 흐름에서 evictAndWarm()이 동기적으로 30회(페이지×정렬×방향) 조회를 수행하므로, 배치 실행 시간이 크게 늘거나 DB 부하/타임아웃으로 스케줄이 밀릴 수 있습니다. 워밍업을 비동기(별도 스레드/이벤트)로 분리하거나, 워밍업 범위를 환경설정으로 제어(페이지 수/정렬 필드 제한)할 수 있게 해두는 것이 운영 안정성에 도움이 됩니다.

Copilot uses AI. Check for mistakes.

return totalSavedCount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package until.the.eternity.auctionhistory.application.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import until.the.eternity.auctionhistory.interfaces.rest.dto.request.AuctionHistorySearchRequest;
import until.the.eternity.common.enums.SortDirection;
import until.the.eternity.common.enums.SortField;
import until.the.eternity.common.request.PageRequestDto;
import until.the.eternity.config.CacheNames;

/**
* 경매 거래 내역 캐시 워밍업 서비스.
*
* <p>AuctionHistoryScheduler 배치 완료 후 호출되어 다음 작업을 수행한다.
*
* <ol>
* <li>auction-history:search 캐시 전체 무효화
* <li>auction_history 기반 역대 랭킹 캐시 무효화 (ALLTIME_HIGHEST, ALLTIME_MONTH_VOLUME)
* <li>page 1~2 × size 20 × sortField 3종 × direction 2종 = 12가지 조합 캐시 워밍
* </ol>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AuctionHistoryCacheWarmupService {

private static final int WARMUP_SIZE = 20;
private static final int WARMUP_MAX_PAGE = 2;

private final AuctionHistoryService auctionHistoryService;
private final CacheManager cacheManager;

/**
* 캐시 무효화 후 기본 12가지 조합(page 1~2 × size 20 × sortField 3종 × direction 2종)을 선제적으로 워밍한다.
*
* <p>빈 검색 조건(필터 없음) 기준으로 워밍하므로, 단순 목록 조회 요청에 즉시 캐시 히트가 발생한다.
*/
public void evictAndWarm() {
evictCaches();
warmup();
}

private void evictCaches() {
clearCache(CacheNames.AUCTION_HISTORY_SEARCH);
// auction_history 전체를 직접 쿼리하는 역대 랭킹도 함께 무효화
clearCache(CacheNames.RANKING_ALLTIME_HIGHEST);
clearCache(CacheNames.RANKING_ALLTIME_MONTH_VOLUME);
log.info(
"[Cache Warmup] Evicted: {}, {}, {}",
CacheNames.AUCTION_HISTORY_SEARCH,
CacheNames.RANKING_ALLTIME_HIGHEST,
CacheNames.RANKING_ALLTIME_MONTH_VOLUME);
}

private void warmup() {
// 옵션 필터가 모두 null인 빈 검색 조건 (캐싱 condition 충족)
AuctionHistorySearchRequest emptyRequest =
new AuctionHistorySearchRequest(
null, null, null, null, null, null, null, null, null);

int successCount = 0;
int failCount = 0;

for (int page = 1; page <= WARMUP_MAX_PAGE; page++) {
for (SortField sortField : SortField.values()) {
for (SortDirection direction : SortDirection.values()) {
PageRequestDto pageRequest =
new PageRequestDto(page, WARMUP_SIZE, sortField, direction);
try {
auctionHistoryService.search(emptyRequest, pageRequest);
successCount++;
} catch (Exception e) {
failCount++;
log.warn(
"[Cache Warmup] Failed: page={}, sortField={}, direction={}",
page,
sortField,
direction,
e);
}
}
}
}

log.info(
"[Cache Warmup] Completed: success={}, fail={} (total={})",
successCount,
failCount,
WARMUP_MAX_PAGE * SortField.values().length * SortDirection.values().length);
}

private void clearCache(String cacheName) {
Cache cache = cacheManager.getCache(cacheName);
if (cache != null) {
cache.clear();
Comment on lines +97 to +98

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

clearCache() calls cache.clear() directly. Unlike @CacheEvict, this bypasses the configured CacheErrorHandler, so a Redis outage can throw a runtime exception here and fail the scheduler flow. Wrap cache.clear() in try/catch (log and continue) or perform eviction via Spring’s cache abstraction that applies the error handler, so warmup/eviction remains best-effort.

Suggested change
if (cache != null) {
cache.clear();
if (cache == null) {
log.warn("[Cache Warmup] Cache not found for eviction: {}", cacheName);
return;
}
try {
cache.clear();
} catch (RuntimeException ex) {
log.warn("[Cache Warmup] Failed to clear cache: {}", cacheName, ex);

Copilot uses AI. Check for mistakes.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -15,6 +16,7 @@
import until.the.eternity.auctionhistory.interfaces.rest.dto.response.ItemOptionResponse;
import until.the.eternity.common.request.PageRequestDto;
import until.the.eternity.common.response.PageResponseDto;
import until.the.eternity.config.CacheNames;

@Service
@RequiredArgsConstructor
Expand All @@ -25,6 +27,29 @@ public class AuctionHistoryService {
private final AuctionHistoryMapper mapper;
private final EntityManager entityManager;

/**
* 경매 거래 내역을 검색한다.
*
* <p>단순 검색(가격/옵션/인챈트/세공 필터 없음)에 한해 캐싱을 적용한다. TTL 2시간, 배치 완료 시 전체 무효화 + 워밍업.
*/
@Cacheable(
cacheNames = CacheNames.AUCTION_HISTORY_SEARCH,
key =
"#pageRequestDto.page() + ':'"
+ " + #pageRequestDto.size() + ':'"
Comment on lines +38 to +39

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

The cache key uses #pageRequestDto.page() / size() directly. Since PageRequestDto allows these to be null (and toPageable() resolves defaults), requests omitting page/size will generate different keys than requests explicitly using the default values, reducing cache hit rate and fragmenting entries. Consider normalizing in the SpEL key (e.g., (#pageRequestDto.page() ?: 1) / (#pageRequestDto.size() ?: 20) and similarly for sort/direction if they can be null).

Suggested change
"#pageRequestDto.page() + ':'"
+ " + #pageRequestDto.size() + ':'"
"#pageRequestDto.toPageable().pageNumber + ':'"
+ " + #pageRequestDto.toPageable().pageSize + ':'"

Copilot uses AI. Check for mistakes.
+ " + (#pageRequestDto.sortBy() != null ? #pageRequestDto.sortBy().fieldName : 'dateAuctionBuy') + ':'"
+ " + (#pageRequestDto.direction() != null ? #pageRequestDto.direction().code : 'DESC') + ':'"
+ " + (#requestDto.itemName() ?: '') + ':'"
+ " + (#requestDto.isExactItemName() ?: false) + ':'"
+ " + (#requestDto.itemTopCategory() ?: '') + ':'"
+ " + (#requestDto.itemSubCategory() ?: '') + ':'"
+ " + (#requestDto.dateAuctionBuyRequest()?.dateAuctionBuyFrom() ?: '') + ':'"
+ " + (#requestDto.dateAuctionBuyRequest()?.dateAuctionBuyTo() ?: '')",
Comment on lines +35 to +47

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

캐시 키에서 #pageRequestDto.page()/size()/sortBy()/direction()의 null 값을 그대로 문자열에 포함하면, 실제 조회는 toPageable()에서 기본값으로 정규화되는데도 요청 형태(null vs 기본값)별로 서로 다른 캐시 엔트리가 생겨 히트율이 떨어집니다. 키를 (#pageRequestDto.page ?: 1), (#pageRequestDto.size ?: 20)처럼 기본값으로 정규화(또는 pageRequestDto.toPageable() 기반으로 키 구성)해 주세요.

Copilot uses AI. Check for mistakes.
condition =
"#requestDto.itemOptionSearchRequest() == null"
+ " and #requestDto.enchantSearchRequest() == null"
+ " and (#requestDto.metalwareSearchRequests() == null or #requestDto.metalwareSearchRequests().isEmpty())"
+ " and #requestDto.priceSearchRequest() == null")
Comment on lines +35 to +52

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

@Cacheable key/condition for AuctionHistoryService.search doesn't include requestDto.isExactItemName(), but the repository query changes behavior based on that flag (eq vs contains). A cached response for one mode can be incorrectly reused for the other. Include isExactItemName in the cache key (or exclude caching when it’s non-null/true) so exact/like searches are separated.

Copilot uses AI. Check for mistakes.
@Transactional(readOnly = true)
public PageResponseDto<AuctionHistoryDetailResponse<ItemOptionResponse>> search(
AuctionHistorySearchRequest requestDto, PageRequestDto pageRequestDto) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ private BooleanBuilder buildHistoryPredicate(
QAuctionHistoryItemOption mwOpt = new QAuctionHistoryItemOption("mw" + i);
NumberTemplate<Integer> mwLevel =
Expressions.numberTemplate(
Integer.class, "CAST(NULLIF({0}, '') AS integer)", mwOpt.optionValue2);
Integer.class,
"CAST(NULLIF({0}, '') AS integer)",
mwOpt.optionValue2);

var mwSubQuery =
JPAExpressions.select(mwOpt.auctionHistory.auctionBuyId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import until.the.eternity.common.annotation.MetalwareParameters;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
Expand All @@ -15,6 +14,7 @@
import until.the.eternity.auctionhistory.interfaces.rest.dto.request.AuctionHistorySearchRequest;
import until.the.eternity.auctionhistory.interfaces.rest.dto.response.AuctionHistoryDetailResponse;
import until.the.eternity.auctionhistory.interfaces.rest.dto.response.ItemOptionResponse;
import until.the.eternity.common.annotation.MetalwareParameters;
import until.the.eternity.common.request.PageRequestDto;
import until.the.eternity.common.response.PageResponseDto;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
@Schema(description = "경매 거래내역 검색 조건")
public record AuctionHistorySearchRequest(
@Schema(description = "아이템 이름 (like 검색)", example = "페러시우스 타이탄 블레이드") String itemName,
@Schema(description = "아이템 이름 완전 일치 검색 여부 (true: eq, false: like)",
@Schema(
description = "아이템 이름 완전 일치 검색 여부 (true: eq, false: like)",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
defaultValue = "false",
example = "false")
Expand All @@ -18,7 +19,8 @@ public record AuctionHistorySearchRequest(
@Schema(description = "가격 검색 조건") PriceSearchRequest priceSearchRequest,
@Schema(description = "아이템 옵션 검색 조건") ItemOptionSearchRequest itemOptionSearchRequest,
@Schema(description = "인챈트 검색 조건") EnchantSearchRequest enchantSearchRequest,
@Schema(description = "세공 검색 조건 목록 (최대 3개, AND 조건으로 검색)",
@Schema(
description = "세공 검색 조건 목록 (최대 3개, AND 조건으로 검색)",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
hidden = true)
List<MetalwareSearchRequest> metalwareSearchRequests) {}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
public record MetalwareSearchRequest(
@Schema(description = "세공 이름 (완전 일치)", example = "불의 연성술") String metalware,
@Schema(description = "세공 레벨 시작값 (null이면 1로 처리)", example = "1") Integer levelFrom,
@Schema(description = "세공 레벨 종료값 (null이면 30으로 처리)", example = "30")
Integer levelTo) {
@Schema(description = "세공 레벨 종료값 (null이면 30으로 처리)", example = "30") Integer levelTo) {

public int resolvedLevelFrom() {
return levelFrom != null ? levelFrom : 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
* <p>10분 간격으로 Nexon Open API /auction/list를 호출하여 현재 판매 중인 아이템 정보를 수집한다.
*
* <p>각 서브 카테고리별로 전체 데이터를 수집한 뒤, 기존 데이터를 삭제하고 새 데이터로 교체한다. (Full Refresh)
*
* <p>Full Refresh 완료 후 auction-realtime:search 캐시 전체를 무효화한다.
*/
@Slf4j
@Component
Expand Down Expand Up @@ -130,5 +132,8 @@ public void fetchAndSaveAuctionRealtimeAll() {
totalSavedCount,
totalFailedCount,
deletedExpired);

// Full Refresh 완료 후 실시간 경매 검색 캐시 전체 무효화
service.evictSearchCache();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
Expand All @@ -16,6 +18,7 @@
import until.the.eternity.auctionrealtime.interfaces.rest.dto.response.RealtimeItemOptionResponse;
import until.the.eternity.common.enums.ItemCategory;
import until.the.eternity.common.response.PageResponseDto;
import until.the.eternity.config.CacheNames;

/** 실시간 경매장 데이터 Service. */
@Slf4j
Expand All @@ -29,10 +32,20 @@ public class AuctionRealtimeService {
/**
* 실시간 경매장 아이템을 검색한다.
*
* <p>캐시 키: 주요 검색 파라미터 조합. 10분 배치 완료 시 전체 무효화된다.
*
* @param requestDto 검색 조건
* @param pageable 페이지 정보
* @return 검색 결과
*/
@Cacheable(
cacheNames = CacheNames.AUCTION_REALTIME_SEARCH,
key =
"(#requestDto.itemTopCategory() ?: '_') + ':'"
+ " + (#requestDto.itemSubCategory() ?: '_') + ':'"
+ " + (#requestDto.itemName() ?: '_') + ':'"
+ " + (#requestDto.isExactItemName() ?: false) + ':'"
+ " + #pageable.pageNumber + ':' + #pageable.pageSize + ':' + #pageable.sort")

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

AuctionRealtimeSearchRequest includes additional filters (priceSearchRequest, itemOptionSearchRequest, enchantSearchRequest, metalwareSearchRequests), but the cache key only includes category/name/exact + pageable. Because repository.search(requestDto, pageable) uses the full request, different filtered searches can collide on the same cache key and return incorrect cached results. Fix by either (a) adding a condition to cache only when the extra filter fields are null/empty, or (b) incorporating those fields (or a stable hash of the whole request) into the cache key.

Suggested change
+ " + #pageable.pageNumber + ':' + #pageable.pageSize + ':' + #pageable.sort")
+ " + #pageable.pageNumber + ':' + #pageable.pageSize + ':' + #pageable.sort",
condition =
"#requestDto.priceSearchRequest() == null"
+ " && #requestDto.itemOptionSearchRequest() == null"
+ " && #requestDto.enchantSearchRequest() == null"
+ " && (#requestDto.metalwareSearchRequests() == null"
+ " || #requestDto.metalwareSearchRequests().isEmpty())")

Copilot uses AI. Check for mistakes.
@Transactional(readOnly = true)
public PageResponseDto<AuctionRealtimeDetailResponse<RealtimeItemOptionResponse>> search(
AuctionRealtimeSearchRequest requestDto, Pageable pageable) {
Expand Down Expand Up @@ -91,4 +104,14 @@ public int deleteExpiredItems(Instant now) {
log.info("[REALTIME] Deleted {} expired auction realtime items", deleted);
return deleted;
}

/**
* 실시간 경매 검색 캐시 전체를 무효화한다.
*
* <p>AuctionRealtimeScheduler에서 Full Refresh 완료 후 호출한다.
*/
@CacheEvict(cacheNames = CacheNames.AUCTION_REALTIME_SEARCH, allEntries = true)
public void evictSearchCache() {
log.info("[REALTIME] Cache evicted: {}", CacheNames.AUCTION_REALTIME_SEARCH);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import until.the.eternity.common.annotation.MetalwareParameters;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
Expand All @@ -13,6 +12,7 @@
import until.the.eternity.auctionrealtime.interfaces.rest.dto.request.RealtimePageRequestDto;
import until.the.eternity.auctionrealtime.interfaces.rest.dto.response.AuctionRealtimeDetailResponse;
import until.the.eternity.auctionrealtime.interfaces.rest.dto.response.RealtimeItemOptionResponse;
import until.the.eternity.common.annotation.MetalwareParameters;
import until.the.eternity.common.response.PageResponseDto;

@RequestMapping("/auction-realtime")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
@Schema(description = "실시간 경매장 검색 조건")
public record AuctionRealtimeSearchRequest(
@Schema(description = "아이템 이름 (like 검색)", example = "페러시우스 타이탄 블레이드") String itemName,
@Schema(description = "아이템 이름 완전 일치 검색 여부 (true: eq, false: like)",
@Schema(
description = "아이템 이름 완전 일치 검색 여부 (true: eq, false: like)",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
defaultValue = "false",
example = "false")
Expand All @@ -21,7 +22,8 @@ public record AuctionRealtimeSearchRequest(
@Schema(description = "가격 검색 조건") PriceSearchRequest priceSearchRequest,
@Schema(description = "아이템 옵션 검색 조건") ItemOptionSearchRequest itemOptionSearchRequest,
@Schema(description = "인챈트 검색 조건") EnchantSearchRequest enchantSearchRequest,
@Schema(description = "세공 검색 조건 목록 (최대 3개, AND 조건으로 검색)",
@Schema(
description = "세공 검색 조건 목록 (최대 3개, AND 조건으로 검색)",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
hidden = true)
List<MetalwareSearchRequest> metalwareSearchRequests) {}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import until.the.eternity.auctionsearchoption.domain.entity.AuctionSearchOptionMetadata;
import until.the.eternity.auctionsearchoption.domain.repository.AuctionSearchOptionRepositoryPort;
import until.the.eternity.auctionsearchoption.interfaces.rest.dto.response.FieldMetadata;
import until.the.eternity.auctionsearchoption.interfaces.rest.dto.response.SearchOptionMetadataResponse;
import until.the.eternity.config.CacheNames;

@Slf4j
@Service
Expand All @@ -26,6 +28,7 @@ public class AuctionSearchOptionService {
*
* @return 검색 옵션 메타데이터 리스트
*/
@Cacheable(cacheNames = CacheNames.SEARCH_OPTION_ALL_ACTIVE, key = "'all'")
@Transactional(readOnly = true)
public List<SearchOptionMetadataResponse> getAllActiveSearchOptions() {
List<AuctionSearchOptionMetadata> entities = repositoryPort.findAllActive();
Expand Down
Loading