From e729316edfc5226e8057d9afe30238dbfdc50758 Mon Sep 17 00:00:00 2001 From: HeungTak Lee Date: Sat, 7 Feb 2026 07:23:44 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20IntegrationVessel=20=EC=A0=84=EC=9A=A9?= =?UTF-8?q?=20DataSource=20=EC=A7=80=EC=9B=90=20=EB=B0=8F=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=8C=8C=EC=9D=BC=20=ED=98=B8=ED=99=98=EC=84=B1=20?= =?UTF-8?q?=EB=B3=B4=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IntegrationVesselService에 전용 DataSource 자체 생성/관리 추가 - vessel.integration.datasource.* 설정으로 별도 DB 연결 가능 - @PostConstruct에서 경량 HikariDataSource 생성 (max 3, min 1) - 미설정 시 queryDataSource 자동 폴백 (기존 동작 유지) - prod: 별도 DB (mdadb2 gis.t_ship_integration_sub) → 전용 DataSource - dev/prod-mpr: queryDB signal 스키마 → queryDataSource 폴백 - 기존 DataSourceConfig 4개 파일 수정 없이 프로파일 완벽 호환 Co-Authored-By: Claude Opus 4.6 --- docs/implementation-progress.md | 28 +++++++ .../service/IntegrationVesselService.java | 78 +++++++++++++++++-- src/main/resources/application-dev.yml | 2 +- src/main/resources/application-prod.yml | 7 +- src/main/resources/application.yml | 5 ++ 5 files changed, 110 insertions(+), 10 deletions(-) diff --git a/docs/implementation-progress.md b/docs/implementation-progress.md index 894ae78..a092157 100644 --- a/docs/implementation-progress.md +++ b/docs/implementation-progress.md @@ -169,6 +169,30 @@ --- +## Phase 7 — 통합선박(IntegrationVessel) 전용 DataSource + +- [x] **7.1** IntegrationVesselService 전용 DataSource 지원 + - `vessel.integration.datasource.*` 설정으로 별도 DB 연결 가능 + - `@PostConstruct`에서 전용 HikariDataSource 생성 (max 3, min 1) + - 미설정 시 queryDataSource 자동 폴백 (기존 동작 유지) + - `@PreDestroy`에서 전용 DataSource 정리 + - 상태: **완료** (2026-02-07) + +- [x] **7.2** 설정 기반 테이블명 + - `vessel.integration.table-name` 프로파일별 분리 + - prod: `t_ship_integration_sub` (gis 스키마는 jdbc-url currentSchema) + - dev/prod-mpr/local/query: `signal.t_ship_integration_sub` (기본값) + - 상태: **완료** (2026-02-07) + +- [x] **7.3** 프로파일 호환성 유지 + - prod: 별도 DB (10.188.141.230:5432/mdadb2 gis) → 전용 DataSource + - dev/prod-mpr: queryDB signal 스키마 → queryDataSource 폴백 + - local/query: integration 비활성화 (기본값) + - 기존 DataSourceConfig 4개 파일 수정 없음 + - 상태: **완료** (2026-02-07) + +--- + ## 커밋 이력 | 날짜 | Phase | 커밋 메시지 | 해시 | @@ -180,3 +204,7 @@ | 2026-02-06 | 2.2 | refactor: 쿼리 생명주기 관리 서비스 finally 블록으로 단일화 | 28908e1 | | 2026-02-06 | 3 | perf: 백프레셔 고도화 - 정확한 버퍼 추적 및 적응형 지연 | 7b7e283 | | 2026-02-06 | 4 | feat: WebSocket 설정 외부화 및 부하 제어 모니터링 엔드포인트 | c92bf0e | +| 2026-02-06 | 5 | feat: 대기열 기반 쿼리 관리 및 타임아웃 최적화 | 7bd7bf5 | +| 2026-02-06 | 6 | feat: 일일 데이터 인메모리 캐시 구현 | 03b14e6 | +| 2026-02-06 | 5~6 | docs: Phase 5~6 구현 진행 문서 및 성능 보고서 업데이트 | dc586dd | +| 2026-02-06 | 6 | fix: 캐시-DB 하이브리드 조회 시 뷰포트 2-pass 필터링 정합성 보장 | e9d5d36 | diff --git a/src/main/java/gc/mda/signal_batch/domain/vessel/service/IntegrationVesselService.java b/src/main/java/gc/mda/signal_batch/domain/vessel/service/IntegrationVesselService.java index 4cfe261..094584c 100644 --- a/src/main/java/gc/mda/signal_batch/domain/vessel/service/IntegrationVesselService.java +++ b/src/main/java/gc/mda/signal_batch/domain/vessel/service/IntegrationVesselService.java @@ -1,8 +1,12 @@ package gc.mda.signal_batch.domain.vessel.service; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; import gc.mda.signal_batch.domain.vessel.dto.IntegrationVessel; import gc.mda.signal_batch.global.util.IntegrationSignalConstants; import gc.mda.signal_batch.global.util.IntegrationSignalConstants.SignalType; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -18,6 +22,10 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * 통합선박 정보 서비스 * 글로벌 캐시를 통한 통합선박 정보 관리 + * + * 전용 DataSource 설정 (vessel.integration.datasource.*): + * - jdbc-url이 설정되면 별도 DB에서 통합선박 정보를 로드 + * - 미설정 시 queryDataSource를 폴백으로 사용 */ @Slf4j @Service @@ -25,19 +33,74 @@ public class IntegrationVesselService { private final DataSource queryDataSource; - @Value("${vessel.integration.enabled:true}") + @Value("${vessel.integration.enabled:false}") private boolean integrationEnabled; + @Value("${vessel.integration.datasource.jdbc-url:}") + private String integrationJdbcUrl; + + @Value("${vessel.integration.datasource.username:}") + private String integrationUsername; + + @Value("${vessel.integration.datasource.password:}") + private String integrationPassword; + + @Value("${vessel.integration.datasource.table-name:signal.t_ship_integration_sub}") + private String integrationTableName; + // 글로벌 캐시 (키: "sig_src_cd_target_id") private final Map integrationCache = new ConcurrentHashMap<>(); // 캐시 로드 상태 private final AtomicBoolean cacheLoaded = new AtomicBoolean(false); + // 전용 DataSource (별도 DB 사용 시) + private DataSource integrationDataSource; + private boolean dedicatedDataSource = false; + public IntegrationVesselService(@Qualifier("queryDataSource") DataSource queryDataSource) { this.queryDataSource = queryDataSource; } + @PostConstruct + public void init() { + if (integrationEnabled && integrationJdbcUrl != null && !integrationJdbcUrl.isBlank()) { + try { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(integrationJdbcUrl); + config.setUsername(integrationUsername); + config.setPassword(integrationPassword); + config.setDriverClassName("org.postgresql.Driver"); + config.setMaximumPoolSize(3); + config.setMinimumIdle(1); + config.setPoolName("IntegrationHikariPool"); + config.setConnectionTimeout(10000); + config.setIdleTimeout(300000); + config.setMaxLifetime(600000); + config.setConnectionTestQuery("SELECT 1"); + this.integrationDataSource = new HikariDataSource(config); + this.dedicatedDataSource = true; + log.info("Integration dedicated DataSource created: {}", integrationJdbcUrl); + } catch (Exception e) { + log.warn("Failed to create integration DataSource, falling back to queryDataSource: {}", e.getMessage()); + this.integrationDataSource = queryDataSource; + } + } else { + this.integrationDataSource = queryDataSource; + if (integrationEnabled) { + log.info("Integration using queryDataSource (no dedicated datasource configured)"); + } + } + } + + @PreDestroy + public void destroy() { + if (dedicatedDataSource && integrationDataSource instanceof HikariDataSource hikari) { + hikari.close(); + log.info("Integration dedicated DataSource closed"); + } + } + /** * 통합선박 기능 활성화 여부 */ @@ -137,6 +200,8 @@ public class IntegrationVesselService { status.put("enabled", integrationEnabled); status.put("loaded", cacheLoaded.get()); status.put("size", integrationCache.size()); + status.put("dedicatedDataSource", dedicatedDataSource); + status.put("tableName", integrationTableName); return status; } @@ -161,14 +226,11 @@ public class IntegrationVesselService { long startTime = System.currentTimeMillis(); try { - JdbcTemplate jdbcTemplate = new JdbcTemplate(queryDataSource); + JdbcTemplate jdbcTemplate = new JdbcTemplate(integrationDataSource); - String sql = """ - SELECT intgr_seq, ais, enav, vpass, vts_ais, d_mf_hf, - ais_ship_nm, enav_ship_nm, vpass_ship_nm, vts_ais_ship_nm, d_mf_hf_ship_nm, - integration_ship_ty - FROM signal.t_ship_integration_sub - """; + String sql = "SELECT intgr_seq, ais, enav, vpass, vts_ais, d_mf_hf," + + " ais_ship_nm, enav_ship_nm, vpass_ship_nm, vts_ais_ship_nm, d_mf_hf_ship_nm," + + " integration_ship_ty FROM " + integrationTableName; List vessels = jdbcTemplate.query(sql, (rs, rowNum) -> IntegrationVessel.builder() diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index e698a60..74382be 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -104,7 +104,7 @@ logging: vessel: # spring 하위가 아닌 최상위 레벨 # 통합선박 설정 integration: - enabled: true # 통합선박 기능 활성화 여부 + enabled: true # queryDB signal.t_ship_integration_sub 사용 (기본값) batch: # Area Statistics 처리를 위한 별도 설정 area-statistics: diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 80d8b23..0e26f9c 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -105,7 +105,12 @@ logging: vessel: # spring 하위가 아닌 최상위 레벨 # 통합선박 설정 integration: - enabled: true # 통합선박 기능 활성화 여부 + enabled: true + datasource: + jdbc-url: jdbc:postgresql://10.188.141.230:5432/mdadb2?currentSchema=gis&options=-csearch_path=gis,public&TimeZone=Asia/Seoul + username: mda + password: mda#8932 + table-name: t_ship_integration_sub # gis 스키마는 jdbc-url의 currentSchema로 지정 batch: # Area Statistics 처리를 위한 별도 설정 area-statistics: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 41a5e7b..7421bd3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -136,6 +136,11 @@ vessel: enabled: ${INTEGRATION_ENABLED:false} # 통합선박 기능 활성화 여부 cache: refresh-cron: "0 0 6 * * ?" # 매일 06:00 갱신 + datasource: + jdbc-url: "" # 빈 값: queryDataSource 폴백, 별도 DB 사용 시 프로파일에서 오버라이드 + username: "" + password: "" + table-name: signal.t_ship_integration_sub # 기본: queryDB의 signal 스키마 batch: chunk-size: ${BATCH_CHUNK_SIZE:10000}