【问题标题】:How to access execution context from a Spring Batch Step? Error: No context holder available for job scope如何从 Spring Batch Step 访问执行上下文?错误:没有可用于作业范围的上下文持有者
【发布时间】:2021-02-03 10:25:18
【问题描述】:

我试图在运行时通过将它注入到我的步骤中来设置我的 spring 批处理作业的块大小,如下所示:

@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: No context holder available for job scope

我在网上做了一些研究,但无法理解为什么我会遇到这个异常。我很感激帮助理解这个错误的含义以及如何解决它。谢谢!

【问题讨论】:

  • 有什么理由试图从执行上下文而不是作业参数中获取chunkSize?您如何/何时将 chunkSize 键放入执行上下文中?
  • 我在数据库表中为每个需要运行的作业定义了块大小。块大小通过在作业中此步骤之前运行的 tasklet 放入执行上下文中。我尝试将块大小作为作业参数传递,但我看到有关作业范围的相同问题。

标签: java spring spring-boot spring-batch


【解决方案1】:

我在数据库表中为每个需要运行的作业定义了块大小。块大小通过在作业中此步骤之前运行的 tasklet 放入执行上下文中

这是一个按预期工作的快速示例:

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);
    }

}

这给出了与第一个示例相同的输出。

【讨论】:

  • 感谢您提供示例代码,我能够理解导致错误的原因。我使用 .flow 来定义工作中的步骤顺序,而我应该像上面那样使用 .start。感谢您的帮助!
  • .flow 语法存在一个错误(请参阅github.com/spring-projects/spring-batch/pull/670),该错误将在即将发布的 4.3 版本中修复。
猜你喜欢
  • 2021-11-13
  • 2020-11-29
  • 2021-08-06
  • 2016-06-04
  • 2019-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多