Spring Batch 오류 전파 및 NOOP 해결 방법 (자동 실행 vs 수동 실행)

Spring Batch 실행 중 오류가 main()까지 전달되지 않는 문제NOOP 상태(All steps already completed or no steps configured for this job.) 발생 문제를 해결하는 방법을 정리합니다.
자동 실행(Spring Boot spring.batch.job.enabled=true)과 수동 실행(spring.batch.job.enabled=falseJobLauncher 직접 호출) 방식에 맞게 정리하였습니다.


1. Spring Batch 오류 전파 문제

🔍 문제 상황

  1. Spring Batch 실행 중 Job이 실패했지만 main()까지 예외가 전달되지 않는 문제가 발생할 수 있습니다.
  2. Pod 또는 애플리케이션 모니터링 시스템이 Batch Job이 실패했음을 감지하려면,
    • System.exit(1);을 호출하여 비정상 종료 상태를 전달해야 합니다.
    • 또는 예외를 던져서 main()에서 감지해야 합니다.

2. 자동 실행 (spring.batch.job.enabled=true)에서 오류 전파

✅ 설정 방법

  • application.properties에서 자동 실행을 활성화해야 합니다.
spring.batch.job.enabled=true

✅ 해결 방법: JobExecutionListener에서 오류 감지

Spring Batch는 기본적으로 spring.batch.job.enabled=true 설정 시 자동 실행됩니다.
이 경우, JobExecution을 직접 감지하여 오류 로그를 남길 수 있습니다.

🛠 샘플 코드: JobExecutionListener에서 오류 감지

import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class LoggingJobExecutionListener implements JobExecutionListener {

private static JobExecution lastJobExecution;

@Override
public void afterJob(JobExecution jobExecution) {
lastJobExecution = jobExecution;
if (!jobExecution.getExitStatus().getExitCode().equals("COMPLETED")) {
log.error("🚨 Job Failed: {}", jobExecution.getExitStatus().getExitDescription());
}
}

public static JobExecution getLastJobExecution() {
return lastJobExecution;
}
}

✅ 이제 Batch Job이 실패하면 로그로 오류가 출력됨.
✅ 오류를 main()에서 감지하려면 JobExecutionListener만으로는 부족하며 main()에서 직접 확인해야 함.

✅ 자동 실행에서 오류 전파를 위한 main() 구성

import org.springframework.batch.core.JobExecution;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

public class BatchApplication {
public static void main(String[] args) {
int exitCode = 1;

try {
SpringApplicationBuilder builder = new SpringApplicationBuilder(BatchApplication.class);
ConfigurableApplicationContext context = builder.run(args);

JobExecution jobExecution = LoggingJobExecutionListener.getLastJobExecution();

System.out.println("Job Execution ID: " + jobExecution.getId());
System.out.println("Exit Code: " + jobExecution.getExitStatus().getExitCode());

exitCode = SpringApplication.exit(context);
if (!jobExecution.getExitStatus().getExitCode().equals("COMPLETED")) {
exitCode = 1; // 🚨 Job 실패 시 exitCode 설정
throw new RuntimeException("Batch Job Failed: " + jobExecution.getExitStatus().getExitDescription());
}

System.exit(exitCode);
} catch (Exception e) {
System.err.println("🚨 Batch Job 실행 중 예외 발생: " + e.getMessage());
e.printStackTrace();
System.exit(exitCode);
}
}
}

✅ 이제 자동 실행 상태에서도 오류가 main()까지 전달되며, 실패 시 exitCode가 1로 설정됨.


3. 수동 실행 (spring.batch.job.enabled=false)에서 오류 전파

✅ 설정 방법

  • application.properties에서 자동 실행을 비활성화해야 합니다.
spring.batch.job.enabled=false

✅ 해결 방법: JobLauncher를 직접 실행하여 오류 감지

자동 실행을 비활성화한 후 JobLauncher를 직접 실행하는 경우,
Job 실행 후 JobExecution의 결과를 확인하고 RuntimeException을 던져 main()까지 오류를 전달해야 합니다.

🛠 샘플 코드: main()에서 JobLauncher 직접 실행 후 오류 감지

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

public class BatchApplication {
public static void main(String[] args) {
int exitCode = 1;

try {
SpringApplicationBuilder builder = new SpringApplicationBuilder(BatchApplication.class);
ConfigurableApplicationContext context = builder.run(args);

JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean("sampleJob", Job.class);

JobParameters jobParameters = new JobParameters(); // 실행할 JobParameters 설정
JobExecution jobExecution = jobLauncher.run(job, jobParameters);

System.out.println("Job Execution ID: " + jobExecution.getId());
System.out.println("Exit Code: " + jobExecution.getExitStatus().getExitCode());

exitCode = SpringApplication.exit(context);
if (!jobExecution.getExitStatus().getExitCode().equals("COMPLETED")) {
exitCode = 1; // 🚨 Job 실패 시 exitCode 설정
throw new RuntimeException("Batch Job Failed: " + jobExecution.getExitStatus().getExitDescription());
}

System.exit(exitCode);
} catch (Exception e) {
System.err.println("🚨 Batch Job 실행 중 예외 발생: " + e.getMessage());
e.printStackTrace();
System.exit(exitCode);
}
}
}

✅ 이제 Job 실행 후 실패하면 RuntimeException을 던지고 System.exit(1)이 호출됨.
✅ Pod에서 Crash 상태로 감지될 수 있음.


4. Spring Batch NOOP 상태 해결 방법

🔍 문제: All steps already completed or no steps configured for this job.

Spring Batch는 동일한 JobParameters를 사용하면 기존 실행 기록을 재사용하기 때문에 Job이 실행되지 않고 NOOP 상태가 됩니다.

✅ 해결 방법 1: JobParameterstimestamp 추가 (추천)

JobParameters jobParameters = new JobParametersBuilder()
.addLong("timestamp", System.currentTimeMillis()) // ✅ 매 실행마다 새로운 JobInstance 생성
.toJobParameters();

JobExecution jobExecution = jobLauncher.run(job, jobParameters);

✅ 이제 timestamp 값이 변경되므로, JobInstance가 새롭게 생성되어 실행됨.
✅ 더 이상 NOOP 상태가 발생하지 않고, Job이 실행됨.


✅ 해결 방법 2: RunIdIncrementer 사용

@Bean
public Job sampleJob(JobRepository jobRepository, Step sampleStep, PlatformTransactionManager transactionManager) {
return new JobBuilder("sampleJob", jobRepository)
.incrementer(new RunIdIncrementer()) // ✅ 실행할 때마다 새로운 JobInstance 생성
.start(sampleStep)
.build();
}

✅ 이제 RunIdIncrementer가 자동으로 실행 ID를 증가시키므로, 같은 JobParameters를 사용해도 중복 실행 가능!
✅ 이제 main()에서 JobLauncher.run(job, new JobParameters()); 해도 NOOP 발생하지 않음.


🚀 자동 실행 vs 수동 실행 오류 전파 비교 정리

실행 방식spring.batch.job.enabled오류 전파 방법NOOP 해결 방법
자동 실행 (Spring Boot 기본)truemain()에서 exitCode = 1; 설정 후 RuntimeException 발생RunIdIncrementer() 사용하여 JobParameters 변경
수동 실행 (JobLauncher 직접 호출)falsemain()에서 exitCode = 1; 설정 후 RuntimeException 발생JobParameterstimestamp 추가

✅ 이제 자동 실행과 수동 실행에 따라 오류를 main()까지 전달할 수 있습니다! 🚀

관련 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다