From 9c021f298c2dd8697038ec6e3bb3c61b3fd2f10a Mon Sep 17 00:00:00 2001 From: hyojin kim Date: Fri, 9 Jan 2026 16:07:00 +0900 Subject: [PATCH] :sparkles: Add Ship Detail Sync Job --- .../batch/config/ShipDetailSyncJobConfig.java | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailSyncJobConfig.java diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailSyncJobConfig.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailSyncJobConfig.java new file mode 100644 index 0000000..b671162 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailSyncJobConfig.java @@ -0,0 +1,150 @@ +package com.snp.batch.jobs.shipdetail.batch.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; + +import java.util.Arrays; +import java.util.List; + +@Slf4j +@Configuration +public class ShipDetailSyncJobConfig { + private final JobRepository jobRepository; + private final PlatformTransactionManager transactionManager; + private final JdbcTemplate jdbcTemplate; + + // API 키 정의 (배치 로그 관리용) + protected String getApiKey() { + return "SHIP_DETAIL_SYNC_API"; + } + + // 마지막 실행 일자 업데이트 SQL + protected String getBatchUpdateSql() { + return String.format( + "UPDATE SNP_DATA.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", + getApiKey() + ); + } + + public ShipDetailSyncJobConfig( + JobRepository jobRepository, + PlatformTransactionManager transactionManager, + JdbcTemplate jdbcTemplate) { + this.jobRepository = jobRepository; + this.transactionManager = transactionManager; + this.jdbcTemplate = jdbcTemplate; + } + + /** + * 동기화 대상 25개 테이블 리스트 + */ + private static final List SYNC_TABLES = Arrays.asList( + "additionalshipsdata", "bareboatcharterhistory", + "callsignandmmsihistory", "classhistory", "companycompliancedetails", + "companyvesselrelationships", "crewlist", "darkactivityconfirmed", + "flaghistory", "groupbeneficialownerhistory", "iceclass", "namehistory", + "operatorhistory", "ownerhistory", "pandihistory", "safetymanagementcertificatehist", + "shipmanagerhistory", "sistershiplinks", "specialfeature", "statushistory", + "stowagecommodity", "surveydates", "surveydateshistoryunique", + "technicalmanagerhistory", "thrusters" + ); + + /** + * Job 구성: 모든 테이블 동기화 후 마지막 업데이트 실행 + */ + @Bean(name = "ShipDetailSyncJob") + public Job shipDetailSyncJob() { + return new JobBuilder("ShipDetailSyncJob", jobRepository) + .start(shipMasterAndCoreSyncStep()) // 1단계: Ship_Detail_Data, Core20 테이블 동기화 + .next(shipDetailSyncStep()) // 2단계: 선박제원정보 종속 25개 테이블 순차 동기화 + .next(shipDetailSyncLastExecutionUpdateStep()) // 3단계: 최종 성공 시간 업데이트 + .build(); + } + + /** + * 1단계: Ship_Detail_Data, Core20 테이블 동기화 + */ + @Bean + public Tasklet shipMasterAndCoreSyncTasklet() { + return (contribution, chunkContext) -> { + log.info(">>>>> SHIP MASTER & CORE20 동기화 프로시저 호출 시작"); + + // PostgreSQL 기준 프로시저 호출 (CALL) + jdbcTemplate.execute("CALL snp_data.proc_sync_ship_master_and_core()"); + + log.info(">>>>> SHIP MASTER & CORE20 동기화 프로시저 호출 완료"); + return RepeatStatus.FINISHED; + }; + } + + @Bean(name = "ShipMasterAndCoreSyncStep") + public Step shipMasterAndCoreSyncStep() { + return new StepBuilder("ShipMasterAndCoreSyncStep", jobRepository) + .tasklet(shipMasterAndCoreSyncTasklet(), transactionManager) + .build(); + } + + /** + * 2단계: 25개 테이블 동기화 Tasklet + */ + @Bean + public Tasklet shipDetailSyncTasklet() { + return (contribution, chunkContext) -> { + log.info(">>>>> [시작] 25개 테이블 동기화 프로세스"); + + for (String tableName : SYNC_TABLES) { + try { + log.info("테이블 동기화 중: {}", tableName); + // 이전에 생성한 동적 프로시저 호출 + jdbcTemplate.execute("CALL snp_data.proc_sync_ship_detail('" + tableName + "')"); + } catch (Exception e) { + log.error("테이블 동기화 실패: {}. 에러: {}", tableName, e.getMessage()); + // 특정 테이블 실패 시 중단할지, 계속 진행할지에 따라 throw 여부 결정 + throw e; // 중단하려면 주석 해제 + } + } + + log.info(">>>>> [완료] 25개 테이블 동기화 프로세스"); + return RepeatStatus.FINISHED; + }; + } + + @Bean(name = "ShipDetailSyncStep") + public Step shipDetailSyncStep() { + return new StepBuilder("ShipDetailSyncStep", jobRepository) + .tasklet(shipDetailSyncTasklet(), transactionManager) + .build(); + } + + /** + * 3단계: 모든 스텝 성공 시 배치 실행 로그 업데이트 + */ + @Bean + public Tasklet shipDetailSyncLastExecutionUpdateTasklet() { + return (contribution, chunkContext) -> { + log.info(">>>>> 모든 테이블 동기화 성공: BATCH_LAST_EXECUTION 업데이트 시작"); + + jdbcTemplate.execute(getBatchUpdateSql()); + + log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); + return RepeatStatus.FINISHED; + }; + } + + @Bean(name = "ShipDetailSyncLastExecutionUpdateStep") + public Step shipDetailSyncLastExecutionUpdateStep() { + return new StepBuilder("ShipDetailSyncLastExecutionUpdateStep", jobRepository) + .tasklet(shipDetailSyncLastExecutionUpdateTasklet(), transactionManager) + .build(); + } +} \ No newline at end of file