Spring Batch 입문 8편. JobOperator 깊이 — start·stop·restart·recover·abandon 같은 라이프사이클 제어, 메타데이터 조회 API, v6 신규 CommandLineJobOperator (CLI 표준), Spring Boot 자동 등록, 운영 환경 사용 패턴까지 풀어쓴 학습 노트.
이 글은 Spring Batch 입문에서 운영까지 시리즈 48편 중 8편이에요. 7편 에서 JobRepository(메타데이터 영속 저장소) 의 영속화를 잡았다면, 이번 8편은 그 위에 얹히는 제어 인터페이스인 JobOperator 차례예요.
JobOperator의 자리
[CLI / REST / Spring Scheduler / Kubernetes Job]
↓
[JobOperator] ←── 통합 제어 인터페이스
↓
[JobLauncher] ← Job 실제 실행
↓
[JobRepository] ← 상태 영속
↓
[Job → Step → Reader/Processor/Writer]
외부 시스템이 Job 과 통신할 때면 결국 JobLauncher(Job 실행 진입점) 보다 한 단계 위에 놓인 이 인터페이스를 거치게 돼요.
핵심 메서드 6가지
public interface JobOperator {
Long start(Job job, JobParameters parameters); // 새 실행
Long startNextInstance(Job job); // 자동 incremented parameters
boolean stop(long executionId); // 중지 요청
Long restart(long executionId); // 재시작
Long recover(long executionId); // v6 신규: 수동 복구
Long abandon(long executionId); // 포기 (재시작 불가)
// 외 메타데이터 조회
}
여섯 메서드를 하나씩 짚어 볼게요.
1. start(Job, JobParameters)
새 JobInstance(같은 parameters 묶음의 실행 단위) 를 시작해요.
@Autowired
private JobOperator jobOperator;
@Autowired
private Job myJob;
public void launch() {
JobParameters params = new JobParametersBuilder()
.addString("targetDate", "2026-05-17")
.addLong("run.id", System.currentTimeMillis())
.toJobParameters();
Long executionId = jobOperator.start(myJob, params);
log.info("Started execution {}", executionId);
}
같은 JobParameters(Job 실행에 넘기는 키-값 묶음) 로 두 번 호출하면 두 번째에서 JobInstanceAlreadyCompleteException(같은 instance 가 이미 완료) 이 떨어져요.
2. startNextInstance(Job)
Incrementer(파라미터 자동 증가 컴포넌트) 를 활용해 매번 자동으로 증가된 parameters 로 실행해요.
Long executionId = jobOperator.startNextInstance(myJob);
Job 정의에 RunIdIncrementer(run.id 를 1씩 올리는 기본 구현) 또는 custom incrementer 가 박혀 있어야 동작해요.
3. stop(long executionId)
boolean stopped = jobOperator.stop(executionId);
이름은 stop 이지만 즉시 중지가 아니에요. 지금 돌고 있는 chunk·step 의 자연스러운 종료점까지 끌고 가서 멈추기 때문에 실제로는 수 분 뒤에 멈춰요.
상태도 STOPPING 을 거쳐 STOPPED 로 넘어가요.
4. restart(long executionId)
Long newExecutionId = jobOperator.restart(failedExecutionId);
STOPPED·FAILED 인 같은 JobInstance 위에 새 JobExecution(한 번의 실행 시도 단위) 을 만들고, ExecutionContext(실행 중 상태 저장 맵) 부터 이어 재개해요.
조건:
- 기존 execution =
STOPPED또는FAILED - Job 의
preventRestart()(재시작 차단 설정) 안 박혀 있음
5. recover(long executionId) — v6 신규
Long newExecutionId = jobOperator.recover(failedExecutionId);
FAILED execution 을 RECOVERED 상태로 마킹하고 새 JobExecution 을 만들어요.
언제 쓰냐면, 외부 시스템 cleanup·중복 데이터 제거 같은 수동 정리를 끝낸 뒤 "복구됐다"는 사실을 명시할 때예요.
6. abandon(long executionId)
jobOperator.abandon(executionId);
STOPPED 또는 FAILED execution 을 ABANDONED 로 옮겨 재시작을 영구히 막아요.
데이터 자체가 무효라 재시작 의미가 없을 때 쓰는 카드예요.
메타데이터 조회 API
// Job 이름 목록
Set<String> jobNames = jobOperator.getJobNames();
// 특정 Job 의 JobInstance 들
List<Long> instanceIds = jobOperator.getJobInstances("myJob", 0, 10);
// 특정 instance 의 execution 들
List<Long> executionIds = jobOperator.getExecutions(instanceId);
// 현재 실행 중인 executions
Set<Long> running = jobOperator.getRunningExecutions("myJob");
// 특정 execution 요약
String summary = jobOperator.getSummary(executionId);
// execution context 조회
Map<Long, String> contexts = jobOperator.getStepExecutionSummaries(executionId);
// JobParameters
Properties params = jobOperator.getParameters(executionId);
7편의 JobExplorer(메타데이터 조회 전용 API) 와 영역이 겹치는데, JobOperator 는 거기에 제어까지 묶어 가진 게 차이예요.
Spring Boot 자동 등록
@SpringBootApplication
public class MyApp {
@Autowired
private JobOperator jobOperator; // 자동 주입
}
Spring Boot 가 기본 JobOperator bean 을 알아서 만들어 주기 때문에 별도로 @Bean 을 정의할 필요가 없어요.
CommandLineJobOperator (v6 신규)
CLI 표준 도구로, 4편의 What's New 에서 잠깐 본 자리예요.
기본 사용
# Job 시작
$ java -jar my-batch.jar --job=myJob targetDate=2026-05-17
# Job 중지
$ java -jar my-batch.jar --stop --execution-id=42
# 재시작
$ java -jar my-batch.jar --restart --execution-id=42
# Recover
$ java -jar my-batch.jar --recover --execution-id=42
# Abandon
$ java -jar my-batch.jar --abandon --execution-id=42
# 메타데이터
$ java -jar my-batch.jar --list-jobs
$ java -jar my-batch.jar --list-instances=myJob
$ java -jar my-batch.jar --list-executions=instanceId
$ java -jar my-batch.jar --summary --execution-id=42
활성
@SpringBootApplication
public class BatchCli {
public static void main(String[] args) {
System.exit(SpringApplication.run(BatchCli.class, args).getBean(CommandLineJobOperator.class).run(args));
}
}
이렇게 한 클래스에서 띄우거나, 따로 main class 를 두는 식으로 활성해요.
운영 환경 패턴
# Cron + Kubernetes CronJob
# crontab
0 2 * * * /usr/local/bin/java -jar /opt/batch/my-batch.jar --job=dailyReport targetDate=$(date +%Y-%m-%d)
또는 K8s CronJob(쿠버네티스 cron 스케줄 리소스):
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-report
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: batch
image: my-batch:1.0.0
args:
- --job=dailyReport
- "targetDate=$(date +%Y-%m-%d)"
REST API 통합
@RestController
@RequestMapping("/api/jobs")
public class JobController {
@Autowired
private JobOperator jobOperator;
@Autowired
private JobRegistry jobRegistry;
@PostMapping("/{name}/start")
public ResponseEntity<?> start(@PathVariable String name,
@RequestBody Map<String, String> params) throws Exception {
Job job = jobRegistry.getJob(name);
JobParameters parameters = buildParameters(params);
Long executionId = jobOperator.start(job, parameters);
return ResponseEntity.ok(Map.of("executionId", executionId));
}
@PostMapping("/{executionId}/stop")
public ResponseEntity<?> stop(@PathVariable long executionId) throws Exception {
boolean stopped = jobOperator.stop(executionId);
return ResponseEntity.ok(Map.of("stopped", stopped));
}
@PostMapping("/{executionId}/restart")
public ResponseEntity<?> restart(@PathVariable long executionId) throws Exception {
Long newId = jobOperator.restart(executionId);
return ResponseEntity.ok(Map.of("newExecutionId", newId));
}
@GetMapping("/{executionId}")
public ResponseEntity<?> status(@PathVariable long executionId) {
String summary = jobOperator.getSummary(executionId);
return ResponseEntity.ok(summary);
}
}
여기에 JobRegistry(이름으로 Job 을 찾는 레지스트리) 를 같이 끼우면 운영 dashboard·monitoring API 의 기반이 그대로 잡혀요.
Spring Cloud Data Flow 통합
대규모 운영은 Spring Cloud Data Flow (SCDF) 로 풀어요. Job 등록·실행·모니터링·재시작을 UI·CLI·API 한 묶음으로 묶은, JobOperator 위에 한 겹 더 얹은 추상화예요.
SCDF(Spring Cloud Data Flow 의 약자) 는 복잡한 미세 제어가 없어도 되는 환경에서 강한데, 회사 환경에 따라 SCDF 를 그대로 쓰거나 REST + UI 를 직접 짜는 쪽으로 갈려요.
Exception 처리
try {
Long executionId = jobOperator.start(job, parameters);
} catch (JobInstanceAlreadyCompleteException e) {
// 같은 parameters 의 JobInstance 가 이미 COMPLETED
log.error("Already completed", e);
} catch (JobExecutionAlreadyRunningException e) {
// 같은 JobInstance 가 실행 중
log.error("Already running", e);
} catch (JobRestartException e) {
// 재시작 불가 (preventRestart 또는 ABANDONED)
log.error("Cannot restart", e);
} catch (JobParametersInvalidException e) {
// Validator 실패
log.error("Invalid parameters", e);
}
각 exception 의 의미를 명확히 잡아 두면 운영 대응이 그만큼 빨라져요.
운영 시나리오
시나리오 1: Long-running Job 정리
# 현재 running execution 확인
$ java -jar batch.jar --running --job=hugeJob
[ executionId=42, started=2026-05-17 02:00, status=STARTED ]
# 안전 중지
$ java -jar batch.jar --stop --execution-id=42
# 수 분 후 확인
$ java -jar batch.jar --summary --execution-id=42
status=STOPPED
# 재시작
$ java -jar batch.jar --restart --execution-id=42
시나리오 2: Failed Job 의 수동 정리
# 실패 확인
$ java -jar batch.jar --summary --execution-id=42
status=FAILED, exception=...
# 외부 시스템 cleanup (다른 process 가 진행)
# ... cleanup 완료
# Recover 마킹 + 새 execution
$ java -jar batch.jar --recover --execution-id=42
시나리오 3: 영구 abandon
# 데이터 자체가 잘못된 경우
$ java -jar batch.jar --abandon --execution-id=42
# 다음 새 instance 시작 (새 parameters)
$ java -jar batch.jar --job=myJob targetDate=2026-05-18
한계·실무 함정
1. Stop 의 지연
stop() 은 즉시가 아니라 chunk·step 의 자연스러운 종료점까지 기다려요. 운영 자동화 코드에서 바로 다음 명령을 이어 던지면 아직 STARTED 인 상태일 수 있어서 polling 이 필요해요.
2. Restart 의 조건
preventRestart() 가 박힌 Job 이나 ABANDONED execution 은 재시작이 안 돼요. exception 처리를 꼭 같이 박아 둬야 해요.
3. ExecutionId 추적
JobOperator API 는 전부 Long executionId 를 키로 받아요. 운영 도구 쪽에서 executionId 를 CI/CD·monitoring DB 어딘가에 보관해 두는 작업이 따라붙어요.
4. JobParameters 의 type
addString·addLong·addDouble·addDate 처럼 타입을 명시해 넣게 돼 있어요. CLI 의 param=value 는 기본이 String 이라 타입 변환을 직접 챙겨 줘야 해요.
5. 동시 운영자 충돌
JobOperator 호출은 DB transaction 안에서 도는데, 여러 operator 가 같은 instance 를 동시에 만지면 race 가 나요. SERIALIZABLE isolation 으로 DB 단에서 차단하는 쪽이 안전해요.
6. CommandLineJobOperator 의 logging
CLI 로 실행하면 Spring Boot startup 로그가 폭증해요. production-ready logging 설정을 같이 잡아 주세요.
logging:
level:
root: WARN
org.springframework.batch: INFO
시험 직전 한 번 더 — JobOperator 함정 압축 노트
- JobOperator = Job 실행·제어·메타데이터 통합 인터페이스
- 6가지 메서드 =
start·startNextInstance·stop·restart·recover·abandon start(Job, JobParameters)= 새 실행, 같은 parameters 두 번 =JobInstanceAlreadyCompleteExceptionstartNextInstance(Job)= Incrementer 활용 자동 parameterstop(executionId)= 즉시 X, chunk 자연 종료점에서 (STOPPING → STOPPED)restart(executionId)= STOPPED·FAILED 의 새 JobExecution, ExecutionContext 부터 재개recover(executionId)(v6 신규) = FAILED → RECOVERED 마킹 + 새 executionabandon(executionId)= 영구 차단 (ABANDONED)- 메타데이터 조회 =
getJobNames·getJobInstances·getExecutions·getRunningExecutions·getSummary·getParameters - Spring Boot 자동 등록 =
@Autowired JobOperator만으로 - CommandLineJobOperator (v6 신규) = CLI 표준
- CLI 명령 —
--job·--stop·--restart·--recover·--abandon·--list-*·--summary - 운영 패턴 — Cron / Kubernetes CronJob + CommandLineJobOperator
- REST API 통합 =
@RestController+ JobOperator 주입 - Spring Cloud Data Flow = 대규모 운영 UI·CLI·API 통합 (옵션)
- Exception —
JobInstanceAlreadyCompleteException·JobExecutionAlreadyRunningException·JobRestartException·JobParametersInvalidException - 시나리오 — Long-running 정리·Failed 수동 cleanup + Recover·영구 abandon
- 함정 — Stop 지연 (polling 필요)
- 함정 — preventRestart·ABANDONED = restart X
- 함정 — JobParameters 의 type 명시
- 함정 — 동시 operator 충돌 (SERIALIZABLE isolation 보장)
- 함정 — CLI logging 폭증
공식 문서: Configuring a JobOperator 에서 원문을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 3편 — Domain Language (Job · JobInstance · Step · Item · Chunk)
- 4편 — v6 What's New + Hello Job 5분 hands-on
- 5편 — Batch Infrastructure (@EnableBatchProcessing · Beans)
- 6편 — Configuring a Job (JobBuilder · Validator · Listener)
- 7편 — JobRepository (영속화 · Schema · Isolation)
다음 글: