Labels

Java (10) Spring (10) Spring MVC (6) Web Services (5) Rest (4) Javascript (3) Nodejs (3) Spring Batch (3) Angular (2) Angular2 (2) Angular6 (2) Expressjs (2) Passportjs (2) SOAP (2) SOAPUI (2) Spring Boot (2) AJAX (1) H2 (1) JQuery (1) JUnit (1) Npm (1) Puppeteer (1) Python (1) RaspberryPi (1) Raspbian (1) SQL (1) SQLite (1) Scripts (1) html (1)

Friday, March 24, 2017

Passing parameters to future steps in Spring Batch

The problem

Imagine that you have a simple spring batch project composed by a job with three steps:



<batch:job id="job1">
  <batch:step id="step1" next="step2">
   <batch:tasklet ref="taskletStep1" transaction-manager="transactionManager"
    start-limit="100">
   </batch:tasklet>
  </batch:step>
  <batch:step id="step2" next="step3">
   <batch:tasklet transaction-manager="transactionManager"
    start-limit="100">
    <batch:chunk reader="readerStep2" writer="writerStep2"
     commit-interval="1" />
   </batch:tasklet>
  </batch:step>
  <batch:step id="step3">
   <batch:tasklet transaction-manager="transactionManager"
    start-limit="100">
    <batch:chunk reader="readerStep3" writer="writerStep3"
     commit-interval="1" />
   </batch:tasklet>
  </batch:step>
 </batch:job>

In many cases steps in Spring Batch tend to be independent processes that do not often communicate with each other. But What if we want to pass an object from step 1 to step 2 or 3?

This is a problem that I encountered in the past and that made me struggle a lot.

Solution

Suppose that you want to pass a String someStringToPass from Step 1, which is composed of a simple tasklet taskletStep1. Then what you need to do is to store this parameter in the so called job context.

You can achieve that by invoking the object ChuckContext which is an input the method RepeatStatus of the interface Tasket that we are implementing in our TaskletStep1. Particularly you will need a sentence like this:



chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().put("someStringToPass", someStringToPass);


In case you need more context, here you have the complete code of an exampe of tasket in which we do this process.


package com.batch.steps;

import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;

@Component("taskletStep1")
public class TaskletStep1 implements Tasklet{
 
 private static final Log log = LogFactory.getLog(TaskletStep1.class);
    
    private String someStringToPass="";
    Random generator = new Random();
    int randomInt = generator.nextInt();
    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {
     log.info("------------------------------------------");
  log.info("Inside step 1");
        
   someStringToPass = "" + randomInt;
   
   //Here is where we storage the parameter in to the context for future steps:
  chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().put("someStringToPass", someStringToPass);
  
  log.info("Passing " + someStringToPass + " to next steps");
  log.info("------------------------------------------");

        return RepeatStatus.FINISHED;
    }
    
     
}


Now someStringToPass is in this "jobContext", so How we invoke it in other steps?



1
. The class in which we invoke it must have the annotation (before public class ...)


@Scope("step")

2. Declare the object that we want to use as a container of the parameter like this:


@Value("#{jobExecutionContext['someStringToPass']}")
private String someStringFromPrevStep;

or alternatively use declare it without the @Value annotation and then include a method like this one:



@BeforeStep                                                                      
  public void retrieveInterstepData(StepExecution stepExecution) {               
      JobExecution jobExecution = stepExecution.getJobExecution();               
      ExecutionContext jobContext = jobExecution.getExecutionContext();          
      this.someStringFromPrevStep = (String) jobContext.get("someStringToPass"); 

Here you have an example of a dummy writer in which we invoke the parameter from a Step1 (where tasket1 is):


package com.batch.steps;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
 * {@link ItemReader} with hard-coded input data.
 */

@Component("readerStep3")
@Scope("step")
public class NoOpItemReaderStep3 implements ItemReader<String> {
 
 private static final Log log = LogFactory.getLog(NoOpItemReaderStep3.class);
 
 @Value("#{jobExecutionContext['someStringToPass']}")
 private String someStringFromPrevStep;
 
 private int index = 0;
 
 /**
  * Reads next record from input
  */
 public String read() throws Exception {
  if (index < 1) {
   log.info("------------------------------------------");
   log.info("Inside step 3");
   log.info("This string is from step 1: "+ someStringFromPrevStep);
   log.info("------------------------------------------");

    index++;
   return "done";
   }
  else {
   return null;
  }
  
 }
// @BeforeStep
//    public void retrieveInterstepData(StepExecution stepExecution) {
//        JobExecution jobExecution = stepExecution.getJobExecution();
//        ExecutionContext jobContext = jobExecution.getExecutionContext();
//        this.someStringFromPrevStep = (String) jobContext.get("someStringToPass");
//    }

}


Here are the logs that prove that Spring took the parameter from one step to the other:



Download the code here:


3 comments: