Spring Batch
Job Parameters - 배치의 운명을 결정짓는 통제 변수
Job Parameters란?
- 정의: 배치 작업에 전달되는 입력 값
- 역할: 배치가 어떤 조건에서, 어떤 데이터를 다룰지를 결정
- 목적: 동적이고 유연한 배치 작업 실행
Job Parameters가 필요한 이유
1. 입력값 동적 변경
- 웹 요청 기반 배치: 요청마다 새로운 Job 실행
- 프로퍼티 한계: 앱 시작 시 한 번 주입되는 정적 값
- Job Parameters 장점: 실행 중 동적 값 변경 가능
2. 메타데이터 관리
- Spring Batch 메타데이터: JobRepository에 모든 값 기록
- Job 인스턴스 식별: 재시작 처리 가능
- 실행 이력 추적: Job과 Step의 실행 정보 관리
- 프로퍼티 한계: 메타데이터로 기록되지 않음
Job Parameters vs 프로퍼티 차이점
| 구분 | Job Parameters | 프로퍼티 |
|---|---|---|
| 동적 변경 | 실행 중 변경 가능 | 앱 시작 시 한 번만 |
| 메타데이터 | Spring Batch 메타데이터로 기록 | 기록되지 않음 |
| 재시작 | Job 인스턴스 식별 및 재시작 가능 | 불가능 |
| 실무 활용 | CI/CD 도구, 스케줄러 지원 | 제한적 |
Job Parameters 전달 방법
커맨드라인에서 전달
./gradlew bootRun --args='--spring.batch.job.name=dataProcessingJob inputFilePath=/data/input/users.csv,java.lang.String'
Job Parameters 표기법
parameterName=parameterValue,parameterType,identificationFlag
구성 요소:
- parameterName: Job에서 파라미터를 찾을 때 사용할 key
- parameterValue: 파라미터의 실제 값
- parameterType: 파라미터 타입 (생략 시 String으로 가정)
- identificationFlag: JobInstance 식별에 사용 여부 (생략 시 true)
여러 파라미터 전달
./gradlew bootRun --args='--spring.batch.job.name=dataProcessingJob inputFilePath=/data/input/users.csv,java.lang.String userCount=5,java.lang.Integer,false'
다양한 타입의 Job Parameters
1) 기본 데이터 타입
@Bean
@StepScope
public Tasklet terminatorTasklet(
@Value("#{jobParameters['terminatorId']}") String terminatorId,
@Value("#{jobParameters['targetCount']}") Integer targetCount
) {
return (contribution, chunkContext) -> {
log.info("ID: {}, 제거 대상 수: {}", terminatorId, targetCount);
return RepeatStatus.FINISHED;
};
}
실행 예
./gradlew bootRun --args='--spring.batch.job.name=processTerminatorJob \
terminatorId=KILL-9,java.lang.String targetCount=5,java.lang.Integer'
@Value("#{jobParameters['키']}")로 주입- 반드시
@StepScope(또는@JobScope)가 붙은 빈에서 주입 (싱글톤 빈에서는 런타임 파라미터 바인딩 불가)
2) 날짜·시간 타입
@Bean
@StepScope
public Tasklet terminatorTasklet(
@Value("#{jobParameters['executionDate']}") LocalDate executionDate,
@Value("#{jobParameters['startTime']}") LocalDateTime startTime
) {
return (contribution, chunkContext) -> {
log.info("처형 예정일: {}", executionDate);
log.info("작전 개시 시각: {}", startTime);
return RepeatStatus.FINISHED;
};
}
실행 예
./gradlew bootRun --args='--spring.batch.job.name=terminatorJob \
executionDate=2024-01-01,java.time.LocalDate \
startTime=2024-01-01T14:30:00,java.time.LocalDateTime'
- 형식:
LocalDate→YYYY-MM-DD(ISO_LOCAL_DATE)LocalDateTime→YYYY-MM-DDTHH:MM:SS(ISO_LOCAL_DATE_TIME)
- 변환은
DefaultJobParametersConverter가 처리
3) Enum 타입
public enum QuestDifficulty { EASY, NORMAL, HARD, EXTREME }
@Bean
@StepScope
public Tasklet terminatorTasklet(
@Value("#{jobParameters['questDifficulty']}") QuestDifficulty questDifficulty
) {
return (contribution, chunkContext) -> {
// questDifficulty 사용
return RepeatStatus.FINISHED;
};
}
실행 예
./gradlew bootRun --args='--spring.batch.job.name=terminatorJob \
questDifficulty=HARD,com.system.batch.TerminatorConfig$QuestDifficulty'
- 내부 클래스 Enum은
$가 포함된 FQN 사용
POJO를 활용한 Job Parameters 주입
SystemInfiltrationParameters
@StepScope
@Component
public class SystemInfiltrationParameters {
@Value("#{jobParameters[missionName]}")
private String missionName;
private int securityLevel;
private final String operationCommander;
public SystemInfiltrationParameters(
@Value("#{jobParameters[operationCommander]}") String operationCommander) {
this.operationCommander = operationCommander;
}
@Value("#{jobParameters[securityLevel]}")
public void setSecurityLevel(int securityLevel) { this.securityLevel = securityLevel; }
// getters...
}
POJO 사용 Tasklet
@Bean
public Tasklet terminatorTasklet(SystemInfiltrationParameters params) {
return (contribution, chunkContext) -> {
int base = 60;
int mult = switch (params.getSecurityLevel()) { case 1->1; case 2->2; case 3->4; case 4->8; default->1; };
int total = base * mult;
log.info("임무: {}, 지휘관: {}, 예상 시간: {}분",
params.getMissionName(), params.getOperationCommander(), total);
return RepeatStatus.FINISHED;
};
}
실행 예
./gradlew bootRun --args='--spring.batch.job.name=terminatorJob \
missionName=안산_데이터센터_침투,java.lang.String \
operationCommander=KILL-9 \
securityLevel=3,java.lang.Integer,false'
- 마지막
false는 해당 파라미터를 Job 인스턴스 식별에서 제외
Job Parameters 주입 방법 요약
- 표현식:
@Value("#{jobParameters['name']}") - 주입 위치: 필드 / 생성자 파라미터 / 세터
- 스코프 필수: 주입 대상 빈에
@StepScope(또는@JobScope) 부착 - 지원 타입: String/숫자/Boolean/Enum/
LocalDate/LocalDateTime등(ISO 표준 형식) - 식별 플래그:
identifying=true/false로 Job 인스턴스 식별 포함 여부 제어(생략 시 true)
JSON 기반 표기법(값에 , 등 특수 문자가 들어갈 때)
기본 표기법은 ,를 타입/플래그 구분에 사용 → 값에 쉼표가 있으면 모호해짐. JSON 표기법 사용.
준비
@Bean
public JobParametersConverter jobParametersConverter() {
return new JsonJobParametersConverter(); // spring-boot-starter-json 필요
}
예시
# bash/gradle bootRun에서 이스케이프 주의
./gradlew bootRun --args="--spring.batch.job.name=terminatorJob \
infiltrationTargets='{\"value\":\"판교서버실,안산데이터센터\",\"type\":\"java.lang.String\"}'"
JAR 실행 예
java -jar app.jar --spring.batch.job.name=terminatorJob \
infiltrationTargets='{"value":"판교_서버실,안산_데이터센터","type":"java.lang.String"}'
프로그래밍 방식으로 JobParameters 생성/전달
JobParameters params = new JobParametersBuilder()
.addJobParameter("inputFilePath", "/data/input/users.csv", String.class) // identifying 기본 true
.addJobParameter("securityLevel", 3, Integer.class, false) // 식별 제외
.toJobParameters();
jobLauncher.run(job, params);
JobParameters 직접 접근(코드에서 조회)
@Component
@Slf4j
public class SystemDestructionTasklet implements Tasklet {
@Override
public RepeatStatus execute(StepContribution c, ChunkContext ctx) {
JobParameters jp = ctx.getStepContext().getStepExecution().getJobParameters();
String target = jp.getString("system.target");
Long level = jp.getLong("system.destruction.level");
log.info("target={}, level={}", target, level);
return RepeatStatus.FINISHED;
}
}
StepExecution→ 부모JobExecution을 통해JobParameters를 노출JobParameters는 불변. 실행 중 변경 값은 ExecutionContext에 저장
커맨드라인 파라미터가 Job으로 전달되는 경로(요약)
- Spring Boot가
JobLauncherApplicationRunner를 자동 실행- 컨텍스트의
Job빈 수집 - 여러 Job이 있으면
--spring.batch.job.name필수 key=value파싱 → 등록된 Converter로JobParameters변환JobLauncher.run(job, params)호출
- 컨텍스트의
파라미터 검증(Validators)
커스텀 Validator
@Component
public class SystemDestructionValidator implements JobParametersValidator {
@Override
public void validate(@Nullable JobParameters p) throws JobParametersInvalidException {
if (p == null) throw new JobParametersInvalidException("파라미터가 NULL입니다");
Long power = p.getLong("destructionPower");
if (power == null) throw new JobParametersInvalidException("destructionPower는 필수");
if (power > 9) throw new JobParametersInvalidException("최대 허용치 초과: " + power);
}
}
@Bean
public Job systemDestructionJob(JobRepository repo, Step step, SystemDestructionValidator v) {
return new JobBuilder("systemDestructionJob", repo)
.validator(v)
.start(step)
.build();
}
DefaultJobParametersValidator
.validator(new DefaultJobParametersValidator(
new String[]{"destructionPower"}, // 필수
new String[]{"targetSystem"} // 선택
))
- 선택 배열을 비우지 않으면: “필수 ∪ 선택”에 정의되지 않은 키 거부
- 필수만 검사하고 나머지 허용:
new DefaultJobParametersValidator(new String[]{"destructionPower"}, new String[]{})
스코프(JobScope/StepScope) 사용 원칙과 주의사항 (매우 중요)

핵심 개념
@JobScope: Job 실행마다 빈 생성/소멸(프록시 기반 지연 생성)@StepScope: Step 실행마다 빈 생성/소멸- 장점: 런타임 파라미터 주입, 동시 실행 간 상태 분리, 실행 종료 시 자원 정리
절대 금지: Job/Step “정의(@Bean)”에 스코프를 붙이지 말 것
- 금지 대상:
@Bean Job ...,@Bean Step ...메서드에@JobScope/@StepScope부착 - 이유

- 스코프 활성화 시점 불일치
- 컨테이너가 Step 메타데이터 초기화를 위해 Step 빈에 실행 전 접근
- 이때
step스코프는 아직 비활성 →ScopeNotActiveException: Scope 'step' is not active for the current thread
- 인프라/확장 기능과 충돌
JobOperator제어, Remote Partitioning 등에서 예기치 못한 실패 가능
- 공식 권고(5.2+)
- “A
Stepbean should not be step-scoped or job-scoped.”
- “A
정석 패턴
- Job/Step 정의는 싱글톤 빈으로 유지
- 파라미터가 필요한 Tasklet/ItemReader/ItemProcessor/ItemWriter 에 **
@StepScope(또는@JobScope)**를 부여하고, Step에서는 해당 빈을 주입받아 사용
// 올바른 예
@Bean
public Step exampleStep(JobRepository repo, PlatformTransactionManager tx,
ItemReader<Foo> reader, ItemWriter<Foo> writer) {
return new StepBuilder("exampleStep", repo)
.<Foo, Foo>chunk(100, tx)
.reader(reader) // reader/processor/writer는 @StepScope 가능
.writer(writer)
.build();
}
@Component
@StepScope
class FooReader implements ItemReader<Foo> {
private final String path;
FooReader(@Value("#{jobParameters['input']}") String path) { this.path = path; }
@Override public Foo read() { /*...*/ return null; }
}
추가 주의사항
- 스코프 프록시는 CGLIB 클래스 프록시 사용 → 클래스는
final이 아니어야 함(상속 가능) @StepScope/@JobScope가 붙은@Bean메서드를 직접 호출하지 말 것(프록시 우회 위험).
→ 빈 주입 방식으로 참조- 불가피하게 메서드 직접 호출이 필요하면, 컴파일 타임 인자를
null로 넘기는 Late Binding이 가능하나, 권장하지 않음
ExecutionContext
역할
- 사용자 상태/중간 결과 저장(메타데이터 저장소에 영속)
- 재시작 시 자동 복원
- Step 간 데이터 공유는 Job 레벨 ExecutionContext를 통해 수행
주입
@Bean
@JobScope
public Tasklet jobScopedTasklet(
@Value("#{jobExecutionContext['previousState']}") String prev) { ... }
@Bean
@StepScope
public Tasklet stepScopedTasklet(
@Value("#{stepExecutionContext['offset']}") Long offset) { ... }
접근 규칙
jobExecutionContext: 동일 Job 내 어디서나 접근 가능stepExecutionContext: 해당 Step 내부에서만 접근 가능- 다른 Step의
stepExecutionContext는 직접 접근 불가 → 공유 필요 시 Job 레벨로 승격하여 저장
최종 정리
- 파라미터 주입 대상 컴포넌트(Tasklet/Reader/Writer/Processor)에
@StepScope/@JobScope부착 - Job/Step 정의 빈에는 스코프 금지(예외 없이 금지)
- 날짜/시간은 ISO 포맷으로 전달 (
LocalDate,LocalDateTime) - 값에
,포함 시 JSON 표기법 +JsonJobParametersConverter identifying=true/false를 정책에 맞게 설계(재실행/인스턴스 식별)- 단순 존재 검증은
DefaultJobParametersValidator, 규칙 검증은 커스텀 Validator JobLauncherApplicationRunner동작 및 Converter 적용 시점 이해
'Spring > Spring Batch' 카테고리의 다른 글
| Spring Batch 3. Spring Batch Listener (3) | 2025.08.15 |
|---|---|
| Spring Batch 1. 테스크릿 지향과 청크지향 처리 (0) | 2025.07.31 |