Spring batch 在SpringBatch中,我们如何在作业的不同步骤之间共享数据?

Spring batch 在SpringBatch中,我们如何在作业的不同步骤之间共享数据?,spring-batch,Spring Batch,深入研究SpringBatch,我想知道如何在作业的不同步骤之间共享数据 我们可以使用JobRepository进行此操作吗?如果是,我们怎么做 有没有其他方法可以做到这一点?从一个步骤中,您可以将数据放入StepExecutionContext。 然后,使用侦听器,您可以将数据从StepExecutionContext提升到JobExecutionContext 此JobExecutionContext在以下所有步骤中都可用 注意:数据必须简短。 这些上下文通过序列化保存在JobReposit

深入研究SpringBatch,我想知道如何在作业的不同步骤之间共享数据

我们可以使用JobRepository进行此操作吗?如果是,我们怎么做


有没有其他方法可以做到这一点?

从一个步骤中,您可以将数据放入
StepExecutionContext
。 然后,使用侦听器,您可以将数据从
StepExecutionContext
提升到
JobExecutionContext

JobExecutionContext
在以下所有步骤中都可用

注意:数据必须简短。 这些上下文通过序列化保存在
JobRepository
中,长度是有限的(如果我记得清楚的话,为2500个字符)

因此,这些上下文很适合共享字符串或简单值,但不适合共享集合或大量数据

共享大量数据不是SpringBatch的理念。
Spring批处理是一组不同的操作,而不是一个庞大的业务处理单元。

作业存储库间接用于在步骤之间传递数据(Jean-Philippe是对的,最好的方法是将数据放入
StepExecutionContext
,然后使用详细命名的键将步骤执行上下文键提升到
JobExecutionContext

值得注意的是,还有一个监听器,用于将
JobParameter
键升级到
StepExecutionContext
(名称更为详细的
JobParameterExecutionContextCopyListener
),如果作业步骤之间不是完全独立的,您会发现经常使用这些监听器

否则,您将使用更复杂的方案在步骤之间传递数据,如JMS队列或(但愿如此)硬编码文件位置


至于在上下文中传递的数据的大小,我还建议您将其保持在较小的范围内(但我没有任何关于可以使用JavaBean对象的细节)

  • 执行一个步骤
  • 将结果存储在Java对象中
  • 下一步将引用相同的java对象,以获得步骤1存储的结果

  • 通过这种方式,您可以存储大量数据,如果您需要的话。

    以下是我为保存一个对象所做的操作,该对象可以通过以下步骤访问

  • 创建了用于在作业上下文中设置对象的侦听器
  • @组件(“myJobListener”)
    公共类MyJobListener实现JobExecutionListener{
    作业前公共无效(作业执行作业执行){
    字符串myValue=someService.getValue();
    jobExecution.getExecutionContext().putString(“我的值”,myValue);
    }
    }
    
  • 在作业上下文中定义了侦听器
  • 
    
  • 使用BeforeStep注释在步骤中使用了该值
  • @BeforeStep
    public void initializeValue(步骤执行步骤执行){
    字符串值=stepExecution.getJobExecution().getExecutionContext().getString(“我的值”);
    }
    
    我想说您有3种选择:

  • 使用
    StepContext
    并将其升级为
    JobContext
    ,您可以从每个步骤访问它,您必须遵守大小限制
  • 创建
    @JobScope
    bean并将数据添加到该bean中,
    @Autowire
    在需要的地方使用它(缺点是它位于内存结构中,如果作业失败,数据丢失,可能会导致可重启性问题)
  • 我们需要跨步骤处理更大的数据集(读取csv中的每一行并写入DB,从DB读取,聚合并发送到API)因此,我们决定在与spring批处理元表相同的DB中对新表中的数据建模,在
    JobContext
    中保留
    ids
    并在需要时访问,在作业成功完成时删除临时表

  • 我被分配了一个任务来逐个调用批处理作业。每个作业都依赖于另一个。第一个作业结果需要执行随后的作业程序。 我正在搜索如何在作业执行后传递数据。我发现这个ExecutionContextPromotionListener很方便

    1) 我为“ExecutionContextPromotionListener”添加了一个bean,如下所示

    @Bean
    public ExecutionContextPromotionListener promotionListener()
    {
        ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
        listener.setKeys( new String[] { "entityRef" } );
        return listener;
    }
    
    lStepContext.put( "entityRef", lMap );
    
    2) 然后我将一个侦听器连接到我的步骤

    Step step = builder.faultTolerant()
                .skipPolicy( policy )
                .listener( writer )
                .listener( promotionListener() )
                .listener( skiplistener )
                .stream( skiplistener )
                .build();
    
    3) 我在Writer步骤实现中添加了stepExecution作为引用,并填充在Beforestep中

    @BeforeStep
    public void saveStepExecution( StepExecution stepExecution )
    {
        this.stepExecution = stepExecution;
    }   
    
    4) 在writer步骤的最后,我将stepexecution中的值填充为如下键

    @Bean
    public ExecutionContextPromotionListener promotionListener()
    {
        ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
        listener.setKeys( new String[] { "entityRef" } );
        return listener;
    }
    
    lStepContext.put( "entityRef", lMap );
    
    5) 作业执行后,我从
    lExecution.getExecutionContext()
    并填充为作业响应

    6) 从job response对象中,我将获取值并在其余作业中填充所需的值

    上面的代码用于使用ExecutionContextPromotionListener将数据从步骤升级到ExecutionContext。
    它可以在任何步骤中完成。

    使用
    ExecutionContextPromotionListener

    public class YourItemWriter implements ItemWriter<Object> {
        private StepExecution stepExecution;
        public void write(List<? extends Object> items) throws Exception {
            // Some Business Logic
    
            // put your data into stepexecution context
            ExecutionContext stepContext = this.stepExecution.getExecutionContext();
            stepContext.put("someKey", someObject);
        }
        @BeforeStep
        public void saveStepExecution(Final StepExecution stepExecution) {
            this.stepExecution = stepExecution;
        }
    }
    
    公共类YourItemWriter实现ItemWriter{
    私人分步执行分步执行;
    
    public void write(List您可以将数据存储在简单对象中。例如:

    AnyObject yourObject = new AnyObject();
    
    public Job build(Step step1, Step step2) {
        return jobBuilderFactory.get("jobName")
                .incrementer(new RunIdIncrementer())
                .start(step1)
                .next(step2)
                .build();
    }
    
    public Step step1() {
        return stepBuilderFactory.get("step1Name")
                .<Some, Any> chunk(someInteger1)
                .reader(itemReader1())
                .processor(itemProcessor1())
                .writer(itemWriter1(yourObject))
                .build();
    }
    
    public Step step2() {
        return stepBuilderFactory.get("step2Name")
                .<Some, Any> chunk(someInteger2)
                .reader(itemReader2())
                .processor(itemProcessor2(yourObject))
                .writer(itemWriter2())
                .build();
    }
    
    AnyObject yourObject=newanyobject();
    公共作业构建(步骤1、步骤2){
    返回jobBuilderFactory.get(“jobName”)
    .incrementer(新的RunIdIncrementer())
    .开始(步骤1)
    .下一步(第2步)
    .build();
    }
    公共步骤第1步(){
    返回stepBuilderFactory.get(“step1Name”)
    .chunk(someInteger1)
    .reader(itemReader1())
    .processor(itemProcessor1())
    .writer(itemWriter1(您的对象))
    .build();
    }
    公共步骤第2步(){
    返回stepBuilderFactory.get(“step2Name”)
    .chunk(someInteger2)
    .reader(itemReader2())
    .processor(itemProcessor2(您的对象))
    .writer(itemWriter2())
    .build();
    }
    
    只需将数据添加到writer或任何其他方法中的对象,并在下一步的任何阶段以Nenad Boz的身份获取数据即可
    AnyObject yourObject = new AnyObject();
    
    public Job build(Step step1, Step step2) {
        return jobBuilderFactory.get("jobName")
                .incrementer(new RunIdIncrementer())
                .start(step1)
                .next(step2)
                .build();
    }
    
    public Step step1() {
        return stepBuilderFactory.get("step1Name")
                .<Some, Any> chunk(someInteger1)
                .reader(itemReader1())
                .processor(itemProcessor1())
                .writer(itemWriter1(yourObject))
                .build();
    }
    
    public Step step2() {
        return stepBuilderFactory.get("step2Name")
                .<Some, Any> chunk(someInteger2)
                .reader(itemReader2())
                .processor(itemProcessor2(yourObject))
                .writer(itemWriter2())
                .build();
    }
    
    class MyTasklet implements Tasklet {
    
        @Override
        public RepeatStatus execute(@NonNull StepContribution contribution, 
                                    @NonNull ChunkContext chunkContext) {
            List<MyObject> myObjects = getObjectsFromSomewhereAndUseThemInNextStep();
            chunkContext.getStepContext().getStepExecution()
            .getJobExecution()
            .getExecutionContext()
            .put("mydatakey", myObjects);
        }
    }
    
    class MyOtherTasklet implements Tasklet {
    
        @Override
        public RepeatStatus execute(@NonNull StepContribution contribution, 
                                    @NonNull ChunkContext chunkContext) {
            List<MyObject> myObjects = (List<MyObject>) 
            chunkContext.getStepContext().getStepExecution()
            .getJobExecution()
            .getExecutionContext()
            .get("mydatakey"); 
        }
    }
    
    class MyReader implements ItemReader<MyObject> {
    
        @Value("#{jobExecutionContext['mydatakey']}")
        List<MyObject> myObjects;
        // And now myObjects are available in here
    
        @Override
        public MyObject read() throws Exception {
    
        }
    }
    
    class MyTasklet : Tasklet {
    
        lateinit var myMap: MutableMap<String, String>
    
        override fun execute(contribution: StepContribution, chunkContext: ChunkContext): RepeatStatus? {
            myMap.put("key", "some value")
            return RepeatStatus.FINISHED
        }
    
    }
    
    @Configuration
    @EnableBatchProcessing
    class BatchConfiguration {
    
        @Autowired
        lateinit var jobBuilderFactory: JobBuilderFactory
    
        @Autowired
        lateinit var stepBuilderFactory: StepBuilderFactory
    
        var myMap: MutableMap<String, String> = mutableMapOf()
    
        @Bean
        fun jobSincAdUsuario(): Job {
            return jobBuilderFactory
                    .get("my-SO-job")
                    .incrementer(RunIdIncrementer())
                    .start(stepMyStep())    
                    .next(stepMyOtherStep())        
                    .build()
        }
    
        @Bean
        fun stepMyStep() = stepBuilderFactory.get("MyTaskletStep")        
            .tasklet(myTaskletAsBean())
            .build()
    
        @Bean
        fun myTaskletAsBean(): MyTasklet {
            val tasklet = MyTasklet()
            tasklet.myMap = myMap      // collection gets visible in the tasklet
            return tasklet
        }
    }
    
    override fun afterPropertiesSet() { Assert.notNull(myMap, "myMap must be set before calling the tasklet") }