英文:
How to access execution context from a Spring Batch Step? Error: No context holder available for job scope
问题
我正在尝试通过将它注入到我的步骤中,来在运行时设置我的 Spring Batch 作业的分块大小,代码如下所示:
@Bean
@JobScope
@Qualifier("myStep")
public Step myStep(@Value("#{jobExecutionContext['chunkSize']}") Integer chunkSize, StepBuilderFactory stepBuilderFactory, ItemReader<Object> reader, ItemWriter<Object> writer, Listener listener) {
return stepBuilderFactory.get("myStep")
.<Object, Object>chunk(chunkSize)
.reader(reader)
.writer(writer)
.listener(listener)
.build();
}
但是我遇到了以下错误:java.lang.IllegalStateException: 作业范围(job scope)下没有可用的上下文持有者
我在网上进行了一些研究,但是无法理解为什么会出现这个异常。我希望能得到帮助,理解这个错误的含义以及如何解决它。谢谢!
<details>
<summary>英文:</summary>
I am trying to set the chunk size of my spring batch job at runtime by injecting it into my step as follows:
@Bean
@JobScope
@Qualifier("myStep")
public Step myStep(@Value("#{jobExecutionContext['chunkSize']}") Integer chunkSize, StepBuilderFactory stepBuilderFactory, ItemReader<Object> reader, ItemWriter<Object> writer, Listener listener) {
return stepBuilderFactory.get("myStep")
.<Object, Object>chunk(chunkSize)
.reader(reader)
.writer(writer)
.listener(listener)
.build();
}
But I am getting the following error: **java.lang.IllegalStateException: No context holder available for job scope**
I've done some research online but am not able to understand why I am hitting this exception. I'd appreciate assistance in understanding what this error means and how to resolve it. Thanks!
</details>
# 答案1
**得分**: 1
我在数据库表中为每个需要运行的作业定义了块大小。块大小通过在作业中的此步骤之前运行的一个任务来放入执行上下文中。
以下是一个符合预期的快速示例:
```java
import java.util.Arrays;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.listener.ChunkListenerSupport;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class SO64447747WithJobExecutionContext {
@Bean
public Step step1(StepBuilderFactory stepBuilderFactory) {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
// TODO get chunk size from table and put in job execution context
chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().put("chunkSize", 2);
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
@JobScope
public Step step2(StepBuilderFactory stepBuilderFactory, @Value("#{jobExecutionContext['chunkSize']}") Integer chunkSize) {
return stepBuilderFactory.get("step2")
.<Integer, Integer>chunk(chunkSize)
.reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4)))
.writer(items -> items.forEach(System.out::println))
.listener(new ChunkListenerSupport() {
@Override
public void beforeChunk(ChunkContext context) {
System.out.println("starting to work on a new chunk of size " + chunkSize);
}
})
.build();
}
@Bean
public Job job(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) {
return jobBuilderFactory.get("job")
.start(step1(stepBuilderFactory))
.next(step2(stepBuilderFactory, null))
.build();
}
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(SO64447747WithJobExecutionContext.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean(Job.class);
jobLauncher.run(job, new JobParameters());
}
}
这将打印以下输出,没有您提到的错误:
starting to work on a new chunk of size 2
1
2
starting to work on a new chunk of size 2
3
4
我尝试将块大小作为作业参数传递,但是我看到了与作业范围相关的相同问题。
当作为作业参数传递chunkSize
时,相同的方法在没有任何异常的情况下工作:
import java.util.Arrays;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.listener.ChunkListenerSupport;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class SO64447747WithJobParameter {
@Bean
@JobScope
public Step step(StepBuilderFactory stepBuilderFactory, @Value("#{jobParameters['chunkSize']}") Integer chunkSize) {
return stepBuilderFactory.get("step")
.<Integer, Integer>chunk(chunkSize)
.reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4)))
.writer(items -> items.forEach(System.out::println))
.listener(new ChunkListenerSupport() {
@Override
public void beforeChunk(ChunkContext context) {
System.out.println("starting to work on a new chunk of size " + chunkSize);
}
})
.build();
}
@Bean
public Job job(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) {
return jobBuilderFactory.get("job")
.start(step(stepBuilderFactory, null))
.build();
}
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(SO64447747WithJobParameter.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean(Job.class);
JobParameters jobParameters = new JobParametersBuilder()
.addLong("chunkSize", 2L)
.toJobParameters();
jobLauncher.run(job, jobParameters);
}
}
这将生成与第一个示例相同的输出。
英文:
> I have the chunk sizes defined in a database table for each job that needs to get run. The chunk size gets put into the execution context through a tasklet that runs before this step in the job
Here is a quick example which works as expected:
import java.util.Arrays;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.listener.ChunkListenerSupport;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class SO64447747WithJobExecutionContext {
@Bean
public Step step1(StepBuilderFactory stepBuilderFactory) {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
// TODO get chunk size from table and put in job execution context
chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().put("chunkSize", 2);
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
@JobScope
public Step step2(StepBuilderFactory stepBuilderFactory, @Value("#{jobExecutionContext['chunkSize']}") Integer chunkSize) {
return stepBuilderFactory.get("step2")
.<Integer, Integer>chunk(chunkSize)
.reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4)))
.writer(items -> items.forEach(System.out::println))
.listener(new ChunkListenerSupport() {
@Override
public void beforeChunk(ChunkContext context) {
System.out.println("starting to work on a new chunk of size " + chunkSize);
}
})
.build();
}
@Bean
public Job job(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) {
return jobBuilderFactory.get("job")
.start(step1(stepBuilderFactory))
.next(step2(stepBuilderFactory, null))
.build();
}
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(SO64447747WithJobExecutionContext.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean(Job.class);
jobLauncher.run(job, new JobParameters());
}
}
This prints the following output without the error you mentioned:
starting to work on a new chunk of size 2
1
2
starting to work on a new chunk of size 2
3
4
> I tried to pass the chunk size as a job parameter but I see the same issue regarding the job scope.
The same approach works without any exception when passing the chunkSize
as a job parameter:
import java.util.Arrays;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.listener.ChunkListenerSupport;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class SO64447747WithJobParameter {
@Bean
@JobScope
public Step step(StepBuilderFactory stepBuilderFactory, @Value("#{jobParameters['chunkSize']}") Integer chunkSize) {
return stepBuilderFactory.get("step")
.<Integer, Integer>chunk(chunkSize)
.reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4)))
.writer(items -> items.forEach(System.out::println))
.listener(new ChunkListenerSupport() {
@Override
public void beforeChunk(ChunkContext context) {
System.out.println("starting to work on a new chunk of size " + chunkSize);
}
})
.build();
}
@Bean
public Job job(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) {
return jobBuilderFactory.get("job")
.start(step(stepBuilderFactory, null))
.build();
}
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(SO64447747WithJobParameter.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean(Job.class);
JobParameters jobParameters = new JobParametersBuilder()
.addLong("chunkSize", 2L)
.toJobParameters();
jobLauncher.run(job, jobParameters);
}
}
This gives the same output as the first example.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论