feat: IntegrationVessel 전용 DataSource 지원 및 프로파일 호환성 보장
- 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 <noreply@anthropic.com>
This commit is contained in:
부모
e9d5d36928
커밋
e729316edf
@ -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 |
|
||||
|
||||
@ -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<String, IntegrationVessel> 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<IntegrationVessel> vessels = jdbcTemplate.query(sql, (rs, rowNum) ->
|
||||
IntegrationVessel.builder()
|
||||
|
||||
@ -104,7 +104,7 @@ logging:
|
||||
vessel: # spring 하위가 아닌 최상위 레벨
|
||||
# 통합선박 설정
|
||||
integration:
|
||||
enabled: true # 통합선박 기능 활성화 여부
|
||||
enabled: true # queryDB signal.t_ship_integration_sub 사용 (기본값)
|
||||
batch:
|
||||
# Area Statistics 처리를 위한 별도 설정
|
||||
area-statistics:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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}
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user