인프런 커뮤니티 질문&답변

이기수님의 프로필 이미지
이기수

작성한 질문수

스프링 배치

JobExplorer / JobRegistry / JobOperator

Step에 @JobScope 걸어두면 stop할때 No context holder available for job scope 에러가 발생합니다.

작성

·

2.5K

0

Step에 @JobScope 걸어두면 stop할때 No context holder available for job scope 에러가 발생합니다.
원래 그런건가요? 아니면 제가 코드를 잘못짠건가요?

답변 3

1

정수원님의 프로필 이미지
정수원
지식공유자

@JobScope 를 선언하게 되면 Step 은 빈이 아닌 프록시로 생성이 됩니다.

이 말은 jobOperator.stop(jobExecution.getId()) 의 실행되는 과정 중에 어떤 시점에서 Step 의 프록시가 아닌 실제 Step 객체를 참조하게 된다면 JobContext 에서 Step 의 객체를 찾게 되는데 해당 Step 을 저장할   JobContext 객체가 생성하기 전에 호출이 발생해서 일어나는 예외입니다.

@JobScope 를 선언하면 Step 마다 새로운 객체가 생성되기 때문에 Step 을 저장하는 JobContext 도 런타임때 생성이 됩니다.

이 부분은 사실 복잡도가 있는 부분입니다.

그리고 @JobScope 는 실시간적으로 웹에서 Job 이나 Step 를 제어하는 곳에서는 객체의 생성시점을 정확하게 동기화하기가 어렵기 때문에 스프링 배치 설정에서만 사용하길 권장합니다.

디버그 하면

그래서 이런 오류가 발생하고 있습니다. 

@JobScope 를 선언하지 않으면 정상작동 합니다.

@JobScope 와 @StepScope 는 강의에서 자세히 설명하고 있으니 참고 바라며 적절한 상황에서 잘 사용해야 하는 기술입니다. 

0

이기수님의 프로필 이미지
이기수
질문자

BatchConfig.java

@Configuration
@RequiredArgsConstructor
public class BatchConfig {
	private final JobRegistry jobRegistry;
	
	@Bean
	public BeanPostProcessor JobRegistryBeanPostProcessor() {
		JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
		jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
		return jobRegistryBeanPostProcessor;
	}
}

 

MyJobConfig.java


@Configuration
@RequiredArgsConstructor
public class MyJobConfig {
	private final JobBuilderFactory jobBuilderFactory;
	private final StepBuilderFactory stepBuilderFactory;
	
	@Bean
	public Job myJob() {
		return jobBuilderFactory.get("myJob")
				.start(myStep1())
				.next(myStep2())
				.build();
	}
	
	@Bean
	@JobScope
	public Step myStep1() {
		return stepBuilderFactory.get("myStep1")
				.tasklet((contribution, context) -> {
					System.out.println("myStep1 start");
					Thread.sleep(10000);
					System.out.println("myStep1 end");
					return RepeatStatus.FINISHED;
				})
				.build();
	}
	
	@Bean
	@JobScope
	public Step myStep2() {
		return stepBuilderFactory.get("myStep2")
				.tasklet((contribution, context) -> {
					System.out.println("myStep2 start");
					Thread.sleep(10000);
					System.out.println("myStep2 end");
					return RepeatStatus.FINISHED;
				})
				.build();
	}
}

 

BatchController.java

@RestController
@RequiredArgsConstructor
@RequestMapping("/batch")
public class BatchController {
	private final JobOperator jobOperator;
	private final JobRegistry jobRegistry;
	private final JobExplorer jobExplorer;
	
	@GetMapping("/start")
	public String jobStart(String data) throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException {
		for(String jobName : jobRegistry.getJobNames()) {
			 jobOperator.start(jobName, "data="+data);
		}
		return "SUCCESS";
	}
	
	@GetMapping("/restart")
	public String jobRestart(String data) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException {
		for(String jobName : jobRegistry.getJobNames()) {
			Long executionId = jobExplorer.getLastJobExecution(jobExplorer.getLastJobInstance(jobName)).getId();
			jobOperator.restart(executionId);
		}
		return "SUCCESS";
	}
	
	@GetMapping("/stop")
	public String jobStop() throws NoSuchJobExecutionException, JobExecutionNotRunningException  {
		for(String jobName : jobRegistry.getJobNames()) {
			for(JobExecution jobExecution : jobExplorer.findRunningJobExecutions(jobName)) {				
				jobOperator.stop(jobExecution.getId());
			}
		}
		return "SUCCESS";
	}
}

 

위 코드에서 http://localhost:8080/batch/start?data=data로 job실행하고, 끝나기전에 http://localhost:8080/batch/stop 호출하면 "java.lang.IllegalStateException: No context holder available for job scope" 에러 발생합니다.

0

정수원님의 프로필 이미지
정수원
지식공유자

전체 코드 좀 볼 수 있을까요?

이기수님의 프로필 이미지
이기수

작성한 질문수

질문하기