package com.ga.common.upload; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.List; import java.util.Map; /** * LOOKUP 변환 규칙용 캐시. * 70만건 업로드 시 행마다 DB 조회를 피하기 위해, 시작 전에 참조 테이블 전체를 Map으로 로드. * * - preload(columns): 매핑 규칙에 LOOKUP이 있으면 해당 테이블을 한번에 캐시 * - lookup(table, sourceField, value, targetField): O(1) 조회 * * Note: 이 캐시는 요청 단위 — 매 업로드마다 새로 만들고 끝나면 GC 대상. * UploadService 내부에서 new LookupCache(uploadMapper) 로 인스턴스화. */ @Slf4j @RequiredArgsConstructor public class LookupCache { private final UploadMapper uploadMapper; private final Map> caches = new HashMap<>(); public void preload(List columns) { columns.stream() .filter(c -> "LOOKUP".equalsIgnoreCase(c.getTransformRule())) .filter(c -> c.getLookupTable() != null && c.getLookupSourceField() != null) .map(c -> c.getLookupTable() + "|" + c.getLookupSourceField() + "|" + c.getLookupTargetField()) .distinct() .forEach(key -> { String[] p = key.split("\\|"); String table = p[0], src = p[1], tgt = p[2]; if (!UploadWhitelist.isAllowedLookupTable(table)) { throw new IllegalArgumentException("허용되지 않은 LOOKUP 테이블: " + table); } List> rows = uploadMapper.selectLookupAll(table, src, tgt); Map map = new HashMap<>(rows.size()); for (Map row : rows) { Object srcVal = row.get("source_value"); Object tgtVal = row.get("target_value"); if (srcVal != null) map.put(srcVal.toString(), tgtVal); } caches.put(key, map); log.info("LookupCache loaded: {} = {} rows", key, map.size()); }); } public Object lookup(String table, String sourceField, String value, String targetField) { if (value == null) return null; Map cache = caches.get(table + "|" + sourceField + "|" + targetField); if (cache == null) return null; return cache.get(value); } public int size() { return caches.size(); } }