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)

Monday, March 27, 2017

Taking decisions inside a Spring Batch Flow

Problem

Suppose you have three steps (first step 1 then 2 and finally 3) in a Spring Batch java project. So imagine that you want to start with Step 1, then do some calculations and then decide if you want to skip step 2 or continue normally.

Something like this:




Solution


What you need is to add a tool called decisor that will run between steps 1 and 2. Let us see how.

The first thing we should do is to create the class that will take the decision. This class will implement the following interface:


\src\main\java\com\hjbello\JobExecutionDecider.java

package com.hjbello;

import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;

public interface JobExecutionDecider {

    FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution);
 
}

This is the class that we will need:

\src\main\java\com\hjbello\FlowDecision.java


package com.hjbello;

import java.util.Random;

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.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.stereotype.Component;

@Component("decider")
public class FlowDecision implements JobExecutionDecider {
     
    private static final Log log = LogFactory.getLog(NoOpItemReaderStep1.class);
    public static final String COMPLETED = "COMPLETED";
    public static final String FAILED = "FAILED";
    
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        Random generator = new Random();
        int randomInt = generator.nextInt();
         
        log.info("Executing Decision with randomInt = " + randomInt);

        if(randomInt % 2 == 0){
         log.info("------------------------------------------");
         log.info("Completed -> go to step 2");
         log.info("------------------------------------------");
            return FlowExecutionStatus.COMPLETED;   
        }
                log.info("------------------------------------------");
                log.info("Failed -> go to step 3");
                log.info("------------------------------------------");
        return FlowExecutionStatus.FAILED;
    }
}


This is a dummy ejemple, what it will do is to obtain a random integer number randomInt;

  • if randomInt is even it will go to step 2 
  • otherwise it will skip step 2 and go to step 3. 
It will actually return a constant (FAILED or COMPLETED) that will be interpreted later inside the flow of the job.

To use this class as a "step" in between 1 and 2 we will have to define our job in the following way. This is the class in which we configure the job

\src\main\java\com\hjbello\BatchConfiguration.java



package com.hjbello;

import javax.batch.api.Decider;
import javax.sql.DataSource;
import org.springframework.batch.core.Job;
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.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

 @Autowired
 public JobBuilderFactory jobBuilderFactory;

 @Autowired
 public StepBuilderFactory stepBuilderFactory;

 @Autowired
 public DataSource dataSource;

 @Bean
 public Job importJob(JobBuilderFactory jobs) {
  FlowBuilder<Flow> flowBuilder = new FlowBuilder<Flow>("flow1");

  Flow flow =  flowBuilder
    .start(step1())
    .next(decision())
    .on(decision().COMPLETED)
    .to(step2())
    .from(decision())
    .on(decision().FAILED)
    .to(step3())
    .end();

  return jobs.get("importUserLoopJob")
    .incrementer(new RunIdIncrementer())
    .start(flow)
    .end()
    .build();
 }

 // STEPS -----------------------------------------

 public Step step1() {
  return stepBuilderFactory.get("step1")
    .<String, String> chunk(10)
    .reader(readerStep1())
    .writer(writerStep1())
    .build();
 } 


 @Bean
 public Step step2() {
  return stepBuilderFactory.get("step2")
    .<String, String> chunk(10)
    .reader(readerStep2())
    .writer(writerStep2())
    .build();
 }

 @Bean
 public Step step3() {
  return stepBuilderFactory.get("step3")
    .tasklet(tasklet())
    .build();
 }

 // TASKLETS READERS AND WRITERS -------------------

 @Bean
 public NoOpItemReaderStep2 readerStep2(){
  return new NoOpItemReaderStep2(); 
 }

 @Bean
 public NoOpItemReaderStep1 readerStep1(){
  return new NoOpItemReaderStep1(); 
 }

 @Bean
 public NoOpItemWriterStep2 writerStep2(){
  return new NoOpItemWriterStep2(); 
 }

 @Bean
 public NoOpItemWriterStep1 writerStep1(){
  return new NoOpItemWriterStep1(); 
 }

 @Bean
 public TaskletStep3 tasklet(){
  return new TaskletStep3();
 }
 @Bean
 public FlowDecision decision(){
  return new FlowDecision();
 }

}



If we want to do this with xml style instead of full-java configuration we would have to use a module-context.xml file like this one:



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:batch="http://www.springframework.org/schema/batch"
 xsi:schemaLocation="
 http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

 <description>Example job to get you started. It provides a skeleton for
  a typical batch application.</description>

 <batch:job id="job1">
  <batch:step id="step1" next="decision">
   <batch:tasklet transaction-manager="transactionManager"
    start-limit="100">
    <batch:chunk reader="readerStep1" writer="writerStep1"
     commit-interval="1" />
   </batch:tasklet>
  </batch:step>
  <batch:decision id="decision" decider="decider">
   <batch:next on="FAILED" to="step3" />
   <batch:next on="COMPLETED" to="step2" />
  </batch:decision>
  <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 ref="taskletStep3" transaction-manager="transactionManager"
    start-limit="100">
   </batch:tasklet>
  </batch:step>
 </batch:job>


</beans>


Lets see how it runs:

  • It starts with step 1:



  • Since the number is even commands the flow to go to step 2




  • Starts step 2




  • Ends with step 3.




Download the code here:




1 comment:

  1. Great. I have different scenario if the random value is even it will execute step 2 and step 3. The value is odd it will skip step 2 and execute step 3

    ReplyDelete