diff --git a/src/main/java/gc/mda/signal_batch/domain/gis/service/GisServiceV2.java b/src/main/java/gc/mda/signal_batch/domain/gis/service/GisServiceV2.java index ecefb2b..8036216 100644 --- a/src/main/java/gc/mda/signal_batch/domain/gis/service/GisServiceV2.java +++ b/src/main/java/gc/mda/signal_batch/domain/gis/service/GisServiceV2.java @@ -24,6 +24,7 @@ import org.springframework.stereotype.Service; import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -327,7 +328,7 @@ public class GisServiceV2 { Set requestedMmsis = new HashSet<>(request.getVessels()); - // 1. 캐시에서 조회 (캐시된 날짜) + // 1. 캐시에서 조회 (캐시된 날짜) + 누락 MMSI 부분 DB fallback if (split.hasCachedData()) { List cachedTracks = dailyTrackCacheManager.getCachedTracksMultipleDays(split.getCachedDates()); @@ -343,6 +344,28 @@ public class GisServiceV2 { allTracks.addAll(filteredCached); log.debug("[CacheQuery] cached {} days -> {} tracks (filtered from {})", split.getCachedDates().size(), filteredCached.size(), totalCachedCount); + + // Daily 캐시에 없는 MMSI → DB fallback (hourly/5min 계층 조회) + Set cachedMmsis = filteredCached.stream() + .map(CompactVesselTrack::getVesselId) + .collect(Collectors.toSet()); + Set missingMmsis = new HashSet<>(requestedMmsis); + missingMmsis.removeAll(cachedMmsis); + if (!missingMmsis.isEmpty()) { + LocalDate minDate = split.getCachedDates().stream().min(Comparator.naturalOrder()).orElse(null); + LocalDate maxDate = split.getCachedDates().stream().max(Comparator.naturalOrder()).orElse(null); + if (minDate != null) { + LocalDateTime cacheStart = minDate.atStartOfDay().isBefore(startTime) ? startTime : minDate.atStartOfDay(); + LocalDateTime cacheEnd = maxDate.plusDays(1).atStartOfDay().isAfter(endTime) ? endTime : maxDate.plusDays(1).atStartOfDay(); + VesselTracksRequest fallbackReq = VesselTracksRequest.builder() + .startTime(cacheStart).endTime(cacheEnd) + .vessels(new ArrayList<>(missingMmsis)).build(); + List dbResult = gisService.getVesselTracks(fallbackReq); + allTracks.addAll(dbResult); + log.info("[CACHE-MONITOR] queryWithCache daily PARTIAL → DB fallback: cacheHit={}, cacheMiss={}, dbTracks={}", + cachedMmsis.size(), missingMmsis.size(), dbResult.size()); + } + } } // 2. DB에서 조회 (캐시 미적중 과거 날짜) diff --git a/src/main/java/gc/mda/signal_batch/global/util/DateTimeParseUtil.java b/src/main/java/gc/mda/signal_batch/global/util/DateTimeParseUtil.java index d6648e1..525597d 100644 --- a/src/main/java/gc/mda/signal_batch/global/util/DateTimeParseUtil.java +++ b/src/main/java/gc/mda/signal_batch/global/util/DateTimeParseUtil.java @@ -1,6 +1,7 @@ package gc.mda.signal_batch.global.util; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -76,11 +77,12 @@ public class DateTimeParseUtil { String trimmed = dateTimeString.trim(); // Try zoned formatters first for Z or offset patterns + // UTC/Offset → 시스템 타임존(KST)으로 변환 후 LocalDateTime 추출 if (trimmed.contains("Z") || trimmed.matches(".*[+-]\\d{2}:\\d{2}$")) { for (DateTimeFormatter formatter : ZONED_FORMATTERS) { try { ZonedDateTime zdt = ZonedDateTime.parse(trimmed, formatter); - return zdt.toLocalDateTime(); + return zdt.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime(); } catch (DateTimeParseException ignored) { // Try next formatter }