diff --git a/src/main/java/com/snp/batch/jobs/compliance/batch/config/CompanyComplianceImportRangeJobConfig.java b/src/main/java/com/snp/batch/jobs/compliance/batch/config/CompanyComplianceImportRangeJobConfig.java index 8449df4..abae783 100644 --- a/src/main/java/com/snp/batch/jobs/compliance/batch/config/CompanyComplianceImportRangeJobConfig.java +++ b/src/main/java/com/snp/batch/jobs/compliance/batch/config/CompanyComplianceImportRangeJobConfig.java @@ -13,6 +13,8 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.job.flow.FlowExecutionStatus; +import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -50,9 +52,15 @@ public class CompanyComplianceImportRangeJobConfig extends BaseMultiStepJobConfi @Value("${app.batch.target-schema.name}") private String targetSchema; + @Value("${app.batch.last-execution-buffer-hours:24}") + private int lastExecutionBufferHours; + protected String getApiKey() {return "COMPANY_COMPLIANCE_IMPORT_API";} protected String getBatchUpdateSql() { - return String.format("UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", targetSchema, getApiKey());} + return String.format( + "UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'", + targetSchema, lastExecutionBufferHours, getApiKey()); + } @Override protected int getChunkSize() { @@ -90,12 +98,26 @@ public class CompanyComplianceImportRangeJobConfig extends BaseMultiStepJobConfi @Override protected Job createJobFlow(JobBuilder jobBuilder) { return jobBuilder - .start(companyComplianceImportRangeStep()) // 1단계 실행 -// .next(companyComplianceHistoryValueChangeManageStep()) // 2단계 실행 (2단계 실패 시 실행 안 됨) - .next(companyComplianceLastExecutionUpdateStep()) // 3단계: 모두 완료 시, BATCH_LAST_EXECUTION 마지막 성공일자 업데이트 + .start(companyComplianceImportRangeStep()) + .next(companyComplianceEmptyResponseDecider()) + .on("EMPTY_RESPONSE").end() + .from(companyComplianceEmptyResponseDecider()).on("*").to(companyComplianceLastExecutionUpdateStep()) + .end() .build(); } + @Bean + public JobExecutionDecider companyComplianceEmptyResponseDecider() { + return (jobExecution, stepExecution) -> { + if (stepExecution != null && stepExecution.getReadCount() == 0) { + log.info("[CompanyComplianceImportRangeJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵"); + return new FlowExecutionStatus("EMPTY_RESPONSE"); + } + log.info("[CompanyComplianceImportRangeJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행"); + return new FlowExecutionStatus("NORMAL"); + }; + } + @Override protected ItemReader createReader() { return companyComplianceDataRangeReader; @@ -193,11 +215,11 @@ public class CompanyComplianceImportRangeJobConfig extends BaseMultiStepJobConfi @Bean public Tasklet companyComplianceLastExecutionUpdateTasklet() { return (contribution, chunkContext) -> { - log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); + log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours); jdbcTemplate.execute(getBatchUpdateSql()); - log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); + log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours); return RepeatStatus.FINISHED; }; } diff --git a/src/main/java/com/snp/batch/jobs/compliance/batch/config/ComplianceImportRangeJobConfig.java b/src/main/java/com/snp/batch/jobs/compliance/batch/config/ComplianceImportRangeJobConfig.java index 6e09eb6..2445f9a 100644 --- a/src/main/java/com/snp/batch/jobs/compliance/batch/config/ComplianceImportRangeJobConfig.java +++ b/src/main/java/com/snp/batch/jobs/compliance/batch/config/ComplianceImportRangeJobConfig.java @@ -13,6 +13,8 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.job.flow.FlowExecutionStatus; +import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -49,9 +51,15 @@ public class ComplianceImportRangeJobConfig extends BaseMultiStepJobConfig { + if (stepExecution != null && stepExecution.getReadCount() == 0) { + log.info("[ComplianceImportRangeJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵"); + return new FlowExecutionStatus("EMPTY_RESPONSE"); + } + log.info("[ComplianceImportRangeJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행"); + return new FlowExecutionStatus("NORMAL"); + }; + } + @Override protected ItemReader createReader() { return complianceDataRangeReader; @@ -195,11 +217,11 @@ public class ComplianceImportRangeJobConfig extends BaseMultiStepJobConfig { - log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); + log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours); jdbcTemplate.execute(getBatchUpdateSql()); - log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); + log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours); return RepeatStatus.FINISHED; }; } diff --git a/src/main/java/com/snp/batch/jobs/event/batch/config/EventImportJobConfig.java b/src/main/java/com/snp/batch/jobs/event/batch/config/EventImportJobConfig.java index eb636b2..9834a2e 100644 --- a/src/main/java/com/snp/batch/jobs/event/batch/config/EventImportJobConfig.java +++ b/src/main/java/com/snp/batch/jobs/event/batch/config/EventImportJobConfig.java @@ -13,6 +13,8 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.job.flow.FlowExecutionStatus; +import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -45,9 +47,15 @@ public class EventImportJobConfig extends BaseMultiStepJobConfig { + if (stepExecution != null && stepExecution.getReadCount() == 0) { + log.info("[EventImportJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵"); + return new FlowExecutionStatus("EMPTY_RESPONSE"); + } + log.info("[EventImportJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행"); + return new FlowExecutionStatus("NORMAL"); + }; + } + @Bean @StepScope public EventDataReader eventDataReader( @@ -139,11 +162,11 @@ public class EventImportJobConfig extends BaseMultiStepJobConfig { - log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); + log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours); jdbcTemplate.execute(getBatchUpdateSql()); - log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); + log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours); return RepeatStatus.FINISHED; }; } diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/config/PscInspectionJobConfig.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/config/PscInspectionJobConfig.java index f8afa4e..bb37df9 100644 --- a/src/main/java/com/snp/batch/jobs/pscInspection/batch/config/PscInspectionJobConfig.java +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/config/PscInspectionJobConfig.java @@ -15,6 +15,8 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.job.flow.FlowExecutionStatus; +import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -47,9 +49,15 @@ public class PscInspectionJobConfig extends BaseMultiStepJobConfig { + if (stepExecution != null && stepExecution.getReadCount() == 0) { + log.info("[PSCDetailImportJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵"); + return new FlowExecutionStatus("EMPTY_RESPONSE"); + } + log.info("[PSCDetailImportJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행"); + return new FlowExecutionStatus("NORMAL"); + }; + } + @Bean @StepScope public PscApiReader pscApiReader( @@ -145,11 +168,11 @@ public class PscInspectionJobConfig extends BaseMultiStepJobConfig { - log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); + log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours); jdbcTemplate.execute(getBatchUpdateSql()); - log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); + log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours); return RepeatStatus.FINISHED; }; } diff --git a/src/main/java/com/snp/batch/jobs/risk/batch/config/RiskImportRangeJobConfig.java b/src/main/java/com/snp/batch/jobs/risk/batch/config/RiskImportRangeJobConfig.java index 5189e96..8934a9d 100644 --- a/src/main/java/com/snp/batch/jobs/risk/batch/config/RiskImportRangeJobConfig.java +++ b/src/main/java/com/snp/batch/jobs/risk/batch/config/RiskImportRangeJobConfig.java @@ -13,6 +13,8 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.job.flow.FlowExecutionStatus; +import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -45,9 +47,15 @@ public class RiskImportRangeJobConfig extends BaseMultiStepJobConfig { + if (stepExecution != null && stepExecution.getReadCount() == 0) { + log.info("[RiskRangeImportJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵"); + return new FlowExecutionStatus("EMPTY_RESPONSE"); + } + log.info("[RiskRangeImportJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행"); + return new FlowExecutionStatus("NORMAL"); + }; + } + @Override protected ItemReader createReader() { return riskDataRangeReader; @@ -137,11 +160,11 @@ public class RiskImportRangeJobConfig extends BaseMultiStepJobConfig { - log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); + log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours); jdbcTemplate.execute(getBatchUpdateSql()); - log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); + log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours); return RepeatStatus.FINISHED; }; } 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 index e4aae3b..ce3abe8 100644 --- 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 @@ -28,6 +28,9 @@ public class ShipDetailSyncJobConfig { @Value("${app.batch.target-schema.name}") private String targetSchema; + @Value("${app.batch.last-execution-buffer-hours:24}") + private int lastExecutionBufferHours; + // API 키 정의 (배치 로그 관리용) protected String getApiKey() { return "SHIP_DETAIL_SYNC_API"; @@ -36,8 +39,8 @@ public class ShipDetailSyncJobConfig { // 마지막 실행 일자 업데이트 SQL protected String getBatchUpdateSql() { return String.format( - "UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", - targetSchema, getApiKey() + "UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'", + targetSchema, lastExecutionBufferHours, getApiKey() ); } @@ -139,11 +142,11 @@ public class ShipDetailSyncJobConfig { @Bean public Tasklet shipDetailSyncLastExecutionUpdateTasklet() { return (contribution, chunkContext) -> { - log.info(">>>>> 모든 테이블 동기화 성공: BATCH_LAST_EXECUTION 업데이트 시작"); + log.info(">>>>> 모든 테이블 동기화 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours); jdbcTemplate.execute(getBatchUpdateSql()); - log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); + log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours); return RepeatStatus.FINISHED; }; } diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailUpdateJobConfig.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailUpdateJobConfig.java index 5b05301..ae83038 100644 --- a/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailUpdateJobConfig.java +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailUpdateJobConfig.java @@ -69,9 +69,15 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig { - log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); + log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours); jdbcTemplate.execute(getBatchUpdateSql()); - log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); + log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours); return RepeatStatus.FINISHED; }; } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index b65a905..41f23a7 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -110,6 +110,9 @@ app: enabled: true cron: "0 0 * * * ?" # Every hour + # LAST_EXECUTION 버퍼 시간 (시간 단위) - 외부 DB 동기화 지연 대응 + last-execution-buffer-hours: 24 + # ShipDetailUpdate 배치 설정 (dev - 기존과 동일하게 20건 유지) ship-detail-update: batch-size: 20 # dev에서는 문제 없으므로 기존 20건 유지 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index f980c6c..bdce1c6 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -112,6 +112,9 @@ app: enabled: true cron: "0 0 * * * ?" # Every hour + # LAST_EXECUTION 버퍼 시간 (시간 단위) - 외부 DB 동기화 지연 대응 + last-execution-buffer-hours: 24 + # ShipDetailUpdate 배치 설정 (prod 튜닝) ship-detail-update: batch-size: 10 # API 요청 당 IMO 건수 (프록시 타임아웃 방지) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 78f8155..53f8e62 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -165,6 +165,9 @@ app: enabled: true cron: "0 0 * * * ?" # Every hour + # LAST_EXECUTION 버퍼 시간 (시간 단위) - 외부 DB 동기화 지연 대응 + last-execution-buffer-hours: 24 + # ShipDetailUpdate 배치 설정 ship-detail-update: batch-size: 10 # API 요청 당 IMO 건수