feat: 데이터 수집 누락 방지 프로세스 추가

This commit is contained in:
hyojin kim 2026-02-27 17:28:33 +09:00
부모 481b14a98c
커밋 c6b5c230ab
7개의 변경된 파일161개의 추가작업 그리고 32개의 파일을 삭제

파일 보기

@ -13,6 +13,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step; import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder; 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.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.core.step.tasklet.Tasklet;
@ -50,9 +52,15 @@ public class CompanyComplianceImportRangeJobConfig extends BaseMultiStepJobConfi
@Value("${app.batch.target-schema.name}") @Value("${app.batch.target-schema.name}")
private String targetSchema; private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "COMPANY_COMPLIANCE_IMPORT_API";} protected String getApiKey() {return "COMPANY_COMPLIANCE_IMPORT_API";}
protected String getBatchUpdateSql() { 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 @Override
protected int getChunkSize() { protected int getChunkSize() {
@ -90,12 +98,26 @@ public class CompanyComplianceImportRangeJobConfig extends BaseMultiStepJobConfi
@Override @Override
protected Job createJobFlow(JobBuilder jobBuilder) { protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder return jobBuilder
.start(companyComplianceImportRangeStep()) // 1단계 실행 .start(companyComplianceImportRangeStep())
// .next(companyComplianceHistoryValueChangeManageStep()) // 2단계 실행 (2단계 실패 실행 ) .next(companyComplianceEmptyResponseDecider())
.next(companyComplianceLastExecutionUpdateStep()) // 3단계: 모두 완료 , BATCH_LAST_EXECUTION 마지막 성공일자 업데이트 .on("EMPTY_RESPONSE").end()
.from(companyComplianceEmptyResponseDecider()).on("*").to(companyComplianceLastExecutionUpdateStep())
.end()
.build(); .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 @Override
protected ItemReader<CompanyComplianceDto> createReader() { protected ItemReader<CompanyComplianceDto> createReader() {
return companyComplianceDataRangeReader; return companyComplianceDataRangeReader;
@ -193,11 +215,11 @@ public class CompanyComplianceImportRangeJobConfig extends BaseMultiStepJobConfi
@Bean @Bean
public Tasklet companyComplianceLastExecutionUpdateTasklet() { public Tasklet companyComplianceLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> { return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql()); jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED; return RepeatStatus.FINISHED;
}; };
} }

파일 보기

@ -13,6 +13,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step; import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder; 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.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.core.step.tasklet.Tasklet;
@ -49,9 +51,15 @@ public class ComplianceImportRangeJobConfig extends BaseMultiStepJobConfig<Compl
@Value("${app.batch.target-schema.name}") @Value("${app.batch.target-schema.name}")
private String targetSchema; private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "COMPLIANCE_IMPORT_API";} protected String getApiKey() {return "COMPLIANCE_IMPORT_API";}
protected String getBatchUpdateSql() { 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 @Override
protected int getChunkSize() { protected int getChunkSize() {
@ -90,12 +98,26 @@ public class ComplianceImportRangeJobConfig extends BaseMultiStepJobConfig<Compl
@Override @Override
protected Job createJobFlow(JobBuilder jobBuilder) { protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder return jobBuilder
.start(complianceImportRangeStep()) // 1단계 실행 .start(complianceImportRangeStep())
// .next(complianceHistoryValueChangeManageStep()) // 2단계 실행 (2단계 실패 실행 ) .next(complianceEmptyResponseDecider())
.next(complianceLastExecutionUpdateStep()) // 3단계: 모두 완료 , BATCH_LAST_EXECUTION 마지막 성공일자 업데이트 .on("EMPTY_RESPONSE").end()
.from(complianceEmptyResponseDecider()).on("*").to(complianceLastExecutionUpdateStep())
.end()
.build(); .build();
} }
@Bean
public JobExecutionDecider complianceEmptyResponseDecider() {
return (jobExecution, stepExecution) -> {
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 @Override
protected ItemReader<ComplianceDto> createReader() { protected ItemReader<ComplianceDto> createReader() {
return complianceDataRangeReader; return complianceDataRangeReader;
@ -195,11 +217,11 @@ public class ComplianceImportRangeJobConfig extends BaseMultiStepJobConfig<Compl
@Bean @Bean
public Tasklet complianceLastExecutionUpdateTasklet() { public Tasklet complianceLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> { return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql()); jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED; return RepeatStatus.FINISHED;
}; };
} }

파일 보기

@ -13,6 +13,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step; import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder; 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.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.core.step.tasklet.Tasklet;
@ -45,9 +47,15 @@ public class EventImportJobConfig extends BaseMultiStepJobConfig<EventDetailDto,
@Value("${app.batch.target-schema.name}") @Value("${app.batch.target-schema.name}")
private String targetSchema; private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "EVENT_IMPORT_API";} protected String getApiKey() {return "EVENT_IMPORT_API";}
protected String getBatchUpdateSql() { 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 @Override
protected int getChunkSize() { protected int getChunkSize() {
@ -87,10 +95,25 @@ public class EventImportJobConfig extends BaseMultiStepJobConfig<EventDetailDto,
protected Job createJobFlow(JobBuilder jobBuilder) { protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder return jobBuilder
.start(eventImportStep()) .start(eventImportStep())
.next(eventLastExecutionUpdateStep()) .next(eventEmptyResponseDecider())
.on("EMPTY_RESPONSE").end()
.from(eventEmptyResponseDecider()).on("*").to(eventLastExecutionUpdateStep())
.end()
.build(); .build();
} }
@Bean
public JobExecutionDecider eventEmptyResponseDecider() {
return (jobExecution, stepExecution) -> {
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 @Bean
@StepScope @StepScope
public EventDataReader eventDataReader( public EventDataReader eventDataReader(
@ -139,11 +162,11 @@ public class EventImportJobConfig extends BaseMultiStepJobConfig<EventDetailDto,
@Bean @Bean
public Tasklet eventLastExecutionUpdateTasklet() { public Tasklet eventLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> { return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql()); jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED; return RepeatStatus.FINISHED;
}; };
} }

파일 보기

@ -15,6 +15,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step; import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder; 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.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.core.step.tasklet.Tasklet;
@ -47,9 +49,15 @@ public class PscInspectionJobConfig extends BaseMultiStepJobConfig<PscInspection
@Value("${app.batch.target-schema.name}") @Value("${app.batch.target-schema.name}")
private String targetSchema; private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "PSC_IMPORT_API";} protected String getApiKey() {return "PSC_IMPORT_API";}
protected String getBatchUpdateSql() { 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());
}
public PscInspectionJobConfig( public PscInspectionJobConfig(
JobRepository jobRepository, JobRepository jobRepository,
@ -87,10 +95,25 @@ public class PscInspectionJobConfig extends BaseMultiStepJobConfig<PscInspection
protected Job createJobFlow(JobBuilder jobBuilder) { protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder return jobBuilder
.start(PSCDetailImportStep()) .start(PSCDetailImportStep())
.next(pscLastExecutionUpdateStep()) .next(pscEmptyResponseDecider())
.on("EMPTY_RESPONSE").end()
.from(pscEmptyResponseDecider()).on("*").to(pscLastExecutionUpdateStep())
.end()
.build(); .build();
} }
@Bean
public JobExecutionDecider pscEmptyResponseDecider() {
return (jobExecution, stepExecution) -> {
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 @Bean
@StepScope @StepScope
public PscApiReader pscApiReader( public PscApiReader pscApiReader(
@ -145,11 +168,11 @@ public class PscInspectionJobConfig extends BaseMultiStepJobConfig<PscInspection
@Bean @Bean
public Tasklet pscLastExecutionUpdateTasklet() { public Tasklet pscLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> { return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql()); jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED; return RepeatStatus.FINISHED;
}; };
} }

파일 보기

@ -13,6 +13,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step; import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder; 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.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.core.step.tasklet.Tasklet;
@ -45,9 +47,15 @@ public class RiskImportRangeJobConfig extends BaseMultiStepJobConfig<RiskDto, Ri
@Value("${app.batch.target-schema.name}") @Value("${app.batch.target-schema.name}")
private String targetSchema; private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "RISK_IMPORT_API";} protected String getApiKey() {return "RISK_IMPORT_API";}
protected String getBatchUpdateSql() { 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 @Override
@ -87,11 +95,26 @@ public class RiskImportRangeJobConfig extends BaseMultiStepJobConfig<RiskDto, Ri
@Override @Override
protected Job createJobFlow(JobBuilder jobBuilder) { protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder return jobBuilder
.start(riskRangeImportStep()) // 1단계: API 데이터 적재 .start(riskRangeImportStep())
.next(riskLastExecutionUpdateStep()) // 2단계: 모두 완료 , BATCH_LAST_EXECUTION 마지막 성공일자 업데이트 .next(riskEmptyResponseDecider())
.on("EMPTY_RESPONSE").end()
.from(riskEmptyResponseDecider()).on("*").to(riskLastExecutionUpdateStep())
.end()
.build(); .build();
} }
@Bean
public JobExecutionDecider riskEmptyResponseDecider() {
return (jobExecution, stepExecution) -> {
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 @Override
protected ItemReader<RiskDto> createReader() { protected ItemReader<RiskDto> createReader() {
return riskDataRangeReader; return riskDataRangeReader;
@ -137,11 +160,11 @@ public class RiskImportRangeJobConfig extends BaseMultiStepJobConfig<RiskDto, Ri
@Bean @Bean
public Tasklet riskLastExecutionUpdateTasklet() { public Tasklet riskLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> { return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql()); jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED; return RepeatStatus.FINISHED;
}; };
} }

파일 보기

@ -28,6 +28,9 @@ public class ShipDetailSyncJobConfig {
@Value("${app.batch.target-schema.name}") @Value("${app.batch.target-schema.name}")
private String targetSchema; private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
// API 정의 (배치 로그 관리용) // API 정의 (배치 로그 관리용)
protected String getApiKey() { protected String getApiKey() {
return "SHIP_DETAIL_SYNC_API"; return "SHIP_DETAIL_SYNC_API";
@ -36,8 +39,8 @@ public class ShipDetailSyncJobConfig {
// 마지막 실행 일자 업데이트 SQL // 마지막 실행 일자 업데이트 SQL
protected String getBatchUpdateSql() { protected String getBatchUpdateSql() {
return String.format( return String.format(
"UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", "UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'",
targetSchema, getApiKey() targetSchema, lastExecutionBufferHours, getApiKey()
); );
} }
@ -139,11 +142,11 @@ public class ShipDetailSyncJobConfig {
@Bean @Bean
public Tasklet shipDetailSyncLastExecutionUpdateTasklet() { public Tasklet shipDetailSyncLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> { return (contribution, chunkContext) -> {
log.info(">>>>> 모든 테이블 동기화 성공: BATCH_LAST_EXECUTION 업데이트 시작"); log.info(">>>>> 모든 테이블 동기화 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql()); jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED; return RepeatStatus.FINISHED;
}; };
} }

파일 보기

@ -69,9 +69,15 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig<ShipDetail
@Value("${app.batch.ship-detail-update.max-retry-count:3}") @Value("${app.batch.ship-detail-update.max-retry-count:3}")
private int maxRetryCount; private int maxRetryCount;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "SHIP_DETAIL_UPDATE_API";} protected String getApiKey() {return "SHIP_DETAIL_UPDATE_API";}
protected String getBatchUpdateSql() { 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());
}
public ShipDetailUpdateJobConfig( public ShipDetailUpdateJobConfig(
@ -114,6 +120,7 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig<ShipDetail
.start(ShipDetailUpdateStep()) .start(ShipDetailUpdateStep())
.next(retryModeDecider()) .next(retryModeDecider())
.on("RETRY").end() .on("RETRY").end()
.from(retryModeDecider()).on("EMPTY_RESPONSE").end()
.from(retryModeDecider()).on("NORMAL").to(shipDetailLastExecutionUpdateStep()) .from(retryModeDecider()).on("NORMAL").to(shipDetailLastExecutionUpdateStep())
.end() .end()
.build(); .build();
@ -131,6 +138,12 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig<ShipDetail
log.info("[ShipDetailUpdateJob] Decider: RETRY 모드 - LAST_EXECUTION 업데이트 스킵"); log.info("[ShipDetailUpdateJob] Decider: RETRY 모드 - LAST_EXECUTION 업데이트 스킵");
return new FlowExecutionStatus("RETRY"); return new FlowExecutionStatus("RETRY");
} }
if (stepExecution != null && stepExecution.getReadCount() == 0) {
log.info("[ShipDetailUpdateJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵 (다음 실행 시 동일 범위 재조회)");
return new FlowExecutionStatus("EMPTY_RESPONSE");
}
log.info("[ShipDetailUpdateJob] Decider: NORMAL 모드 - LAST_EXECUTION 업데이트 진행"); log.info("[ShipDetailUpdateJob] Decider: NORMAL 모드 - LAST_EXECUTION 업데이트 진행");
return new FlowExecutionStatus("NORMAL"); return new FlowExecutionStatus("NORMAL");
}; };
@ -213,11 +226,11 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig<ShipDetail
@Bean @Bean
public Tasklet shipDetailLastExecutionUpdateTasklet() { public Tasklet shipDetailLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> { return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작"); log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql()); jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료"); log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED; return RepeatStatus.FINISHED;
}; };
} }