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

时间:2010-02-18 22:19:26

标签: spring-batch

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

我们可以为此使用JobRepository吗?如果是,我们该怎么做?

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

12 个答案:

答案 0 :(得分:32)

工作存储库间接用于在步骤之间传递数据(Jean-Philippe是正确的,最好的方法是将数据放入StepExecutionContext,然后使用详细命名的ExecutionContextPromotionListener来将步骤执行上下文密钥提升为JobExecutionContext

请注意,还有一个用于向JobParameter推广StepExecutionContext密钥的监听器(更加详细地命名为JobParameterExecutionContextCopyListener);如果你的工作步骤不完全相互独立,你会发现你经常使用这些。

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

关于在上下文中传递的数据的大小,我还建议你保持较小(但我没有任何细节

答案 1 :(得分:27)

从一个步骤,您可以将数据放入StepExecutionContext。 然后,通过监听器,您可以将数据从StepExecutionContext提升为JobExecutionContext

JobExecutionContext可用于以下所有步骤。

笨拙:数据必须很短。 这些上下文通过序列化保存在JobRepository中,并且长度有限(如果我记得很清楚,则为2500个字符)。

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

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

答案 2 :(得分:17)

我会说你有3个选择:

  1. 使用StepContext并将其宣传到JobContext,并且您可以从每个步骤访问它,您必须遵守规定的服从限制
  2. 创建@JobScope bean并向该bean添加数据,@Autowire在需要的地方使用它(缺点是它是内存中的结构,如果作业失败,数据丢失,则会导致问题重启)
  3. 我们需要跨步骤处理更大的数据集(读取csv中的每一行并写入DB,从DB读取,聚合并发送到API)所以我们决定在新表中将数据建模在与Spring batch meta相同的数据库中表,在ids中保留JobContext并在需要时访问,并在作业成功完成后删除该临时表。

答案 3 :(得分:7)

您可以使用Java Bean对象

  1. 执行一步
  2. 将结果存储在Java对象中
  3. 下一步将引用相同的java对象来获取步骤1
  4. 存储的结果

    通过这种方式,您可以存储大量数据(如果需要)

答案 4 :(得分:5)

以下是我为保存可通过以下步骤访问的对象所做的工作。

  1. 创建了一个用于在作业上下文中设置对象的侦听器
  2. @Component("myJobListener")
    public class MyJobListener implements JobExecutionListener {
    
        public void beforeJob(JobExecution jobExecution) {
    
            String myValue = someService.getValue();
            jobExecution.getExecutionContext().putString("MY_VALUE", myValue);
        }
    }
    
    1. 在作业上下文中定义了侦听器
    2. <listeners>
               <listener ref="myJobListener"/>
      </listeners>
      
      1. 使用BeforeStep注释在步骤中消耗了值
      2. @BeforeStep
        public void initializeValues(StepExecution stepExecution) {
        
        String value = stepExecution.getJobExecution().getExecutionContext().getString("MY_VALUE");
        
        }
        

答案 5 :(得分:2)

使用`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;
}
}

现在您需要将promotionListener添加到您的作业

@Bean
public Step step1() {
        return stepBuilder
        .get("step1")<Company,Company>  chunk(10)
        .reader(reader()).processor(processor()).writer(writer())
        .listener(promotionListener()).build();
    }

@Bean
public ExecutionContextPromotionListener promotionListener() {
    ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
    listener.setKeys(new String[] {"someKey"});
    listener.setStrict(true);
    return listener;
}

现在,在步骤2中从作业ExecutionContext获取数据

public class RetrievingItemWriter implements ItemWriter<Object> {
private Object someObject;
public void write(List<? extends Object> items) throws Exception {
// ...
}
@BeforeStep
public void retrieveInterstepData(StepExecution stepExecution) {
JobExecution jobExecution = stepExecution.getJobExecution();
ExecutionContext jobContext = jobExecution.getExecutionContext();
this.someObject = jobContext.get("someKey");
}
}

如果您正在使用tasklet,请使用以下来获取或放置ExecutionContext

List<YourObject> yourObjects = (List<YourObject>) chunkContent.getStepContext().getJobExecutionContext().get("someKey");

答案 6 :(得分:2)

您可以将数据存储在简单对象中。像:

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

只需在编写器或任何其他方法中向对象添加数据,并在下一步的任何阶段获取它

答案 7 :(得分:1)

我被赋予了一个逐个调用批处理作业的任务。每个工作都取决于另一个。第一个工作结果需要执行后续的工作程序。 我正在搜索如何在执行作业后传递数据。我发现这个ExecutionContextPromotionListener派上用场了。

1)我为&#34; ExecutionContextPromotionListener&#34;添加了一个bean。如下所示

@Bean
public ExecutionContextPromotionListener promotionListener()
{
    ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
    listener.setKeys( new String[] { "entityRef" } );
    return listener;
}

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)在我的作家步骤结束时,我将stepexecution中的值填充为如下所示的键

lStepContext.put( "entityRef", lMap );

5)执行作业后,我从中检索了值 lExecution.getExecutionContext()并填充为工作回复。

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

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

答案 8 :(得分:1)

Spring Batch为自己创建元数据表(例如batch_job_executionbatch_job_execution_contextbatch_step_instance等)。

我已经测试(使用postgres DB)在一列(batch_job_execution_context.serialized_content中可以包含至少51,428个字符的数据。可能更多,这只是我测试了多少。

在将Tasklets用于步骤(例如class MyTasklet implements Tasklet)并覆盖其中的RepeatStatus方法时,您可以立即访问ChunkContext

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

现在,您又可以使用另一个Tasklet进行下一步了,您可以在其中访问这些对象

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

或者,如果您没有Tasklet,并且具有类似阅读器/编写器/处理器的功能,那么

class MyReader implements ItemReader<MyObject> {

    @Value("#{jobExecutionContext['mydatakey']}")
    List<MyObject> myObjects;
    // And now myObjects are available in here

    @Override
    public MyObject read() throws Exception {

    }
}

答案 9 :(得分:0)

正如Nenad Bozic在他的第三个选项中所说,使用临时表来共享步骤之间的数据,使用上下文共享也做同样的事情,它写入表并在下一步加载回来,但如果你写入临时表你可以在工作结束时清理。

答案 10 :(得分:0)

另一种非常简单的方法,这里留作以后参考:

class MyTasklet implements Tasklet {
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) {
        getExecutionContext.put("foo", "bar");
    }
}

class MyOtherTasklet implements Tasklet {
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) {
        getExecutionContext.get("foo");
    }   
}

getExecutionContext在这里:

ExecutionContext getExecutionContext(ChunkContext chunkContext) {
    return chunkContext.getStepContext()
                       .getStepExecution()
                       .getJobExecution()
                       .getExecutionContext();
}     

将其作为一个default方法放置在一个超类中,或者作为一个接口放置在您的Tasklet中。

答案 11 :(得分:0)

使用Tasklets的简单解决方案。无需访问执行上下文。我使用地图作为移动的数据元素。 (科特琳代码。)

任务包

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

然后在MyOtherStep中,您可以复制在MyStep中看到的相同习语。另一个Tasklet将看到在MyStep中创建的数据。

重要

  • 任务集是通过@Bean fun创建的,因此它们可以使用@Autowiredfull explanation)。
  • 为实现更可靠的实现,小任务应使用{li>来实现InitializingBean
    override fun afterPropertiesSet() {
        Assert.notNull(myMap, "myMap must be set before calling the tasklet")
    }