Spring Batch : Transitions and Flows

In the previous post we have seen the very basics of a job where we created 1 step and added it to a job and ran it. Lets see how to move from step to step  in a job. Like if step1 completes, do we go to step2 or step3. We should also be able to configure the terminal state, like what happened as the result of this job, was it successfull or did it failed. We must be able to tell spring about these. Lets see this in action, the pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.mynotes.spring.batch</groupId>
<artifactId>batch-trasitions</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>batch-basics</name>
<description>Demo project for Spring batch-trasitions</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

application.properties


spring.datasource.url=jdbc:mysql://localhost:3306/spring_batch?autoReconnect=true&amp;useSSL=false
spring.datasource.username=root
spring.datasource.password=admin

and our launcher class Application.java


package com.mynotes.spring.batch;

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableBatchProcessing
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Lets see our JobConfiguration class


package com.mynotes.spring.batch;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JobConfiguration {

@Autowired
private JobBuilderFactory jobBuilderFactory;

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
public Step step1() {
return stepBuilderFactory.get("step1").tasklet((contribution, chunkContext) -> {
System.out.println("########## STEP 1 ########");
return RepeatStatus.FINISHED;
}).build();
}

@Bean
public Step step2() {
return stepBuilderFactory.get("step2").tasklet((contribution, chunkContext) -> {
System.out.println("########## STEP 2 ########");
return RepeatStatus.FINISHED;
}).build();
}

@Bean
public Step step3() {
return stepBuilderFactory.get("step3").tasklet((contribution, chunkContext) -> {
System.out.println("########## STEP 3 ########");
return RepeatStatus.FINISHED;
}).build();
}

@Bean
public Job transitionJob() {
return jobBuilderFactory.get("transitionJob")
.start(step1())
.next(step2())
.next(step3())
.build();
}

}

Above we have created 3 steps (tasklets). We used the lambda expressions. Then we created a Job which start with step1 and when its finished it goes to step 2 and then step 3. Lets run it

spring-batch-trasition1

You can play by moving around the steps and even repeating steps. Lets see another approach which is configured differently but does the same thing. i have used a different jobname because as seen in the previous post we cannot run a job instance multiple times with the same parameters (in this case we arent sending any so its always null.) You can use the .incrementer(new RunIdIncrementer()) before the start if you want to keep the same jobname.


@Bean
public Job transitionJob() {
return jobBuilderFactory.get("transitionJob1")
.start(step1())
.on("COMPLETED").to(step2())
.from(step2()).on("COMPLETED").to(step3())
.from(step3()).end()
.build();
}

Above we used the .on method passing the exit codes. Spring batch provides 2 different status codes. One is the batch status that is a finite numner of states the spring batch understands. The other is the exit status/code. These are used to decide what to do next. Above we used the “COMPLETED” that is provided by the spring defaults. You can use any string you like provided you return accordingly. We will see in a later post.  .end() is one of the three terminal states (other are .fail() and .stop()) provided by spring batch.The .end tell spring that the step is completed successfully. Actually its doing the same thing as .on(“COMPLETED”) but instead of trasitioning to something else, it is saying the flow is complete.
fail() indicates that the JOB failed at that step.


@Bean
public Job transitionJob() {
return jobBuilderFactory.get("transitionJob2")
.start(step1())
.on("COMPLETED").to(step2())
.from(step2()).on("COMPLETED").fail()
.from(step3()).end()
.build();
}

Above step3 is never executed.

stop() indicates that we are programatically stopping the execution. There is nothing wrong that happened, but stop provides an additional way to restart the job


@Bean
public Job transitionJob() {
return jobBuilderFactory.get("transitionJob3")
.start(step1())
.on("COMPLETED").to(step2())
.from(step2()).on("COMPLETED").stopAndRestart(step3())
.from(step3()).end()
.build();
}

When you run the above job, only step1 and step2 will execute in the first run and the job will stop. When you run it again it will start from step3.

A Flow is a collection of steps and related transitions. Spring batch allows us to create our flow and reuse them as resuable component which can be used in differnet jobs or withing the same job.

Lets say we have a initialization check like to check DB availability or web service availabilty, at the begining of our job. Similarly I have a requirment that after every JOB success i want to send mail to the stakeolders. So I can create steps and make a flow out of it and reuse them in differnet Jobs.  Lets see how to create a flow. FlowConfiguration.java


package com.mynotes.spring.batch;

import org.springframework.batch.core.Step;
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.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FlowConfiguration {
@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
public Step checkDB() {
return stepBuilderFactory.get("checkDB").tasklet((contribution, chunkContext) -> {
System.out.println("########## Checking DB ########");
return RepeatStatus.FINISHED;
}).build();
}

@Bean
public Step checkService() {
return stepBuilderFactory.get("checkService").tasklet((contribution, chunkContext) -> {
System.out.println("########## Checking Service ########");
return RepeatStatus.FINISHED;
}).build();
}

@Bean
public Step sendMail() {
return stepBuilderFactory.get("sendMail").tasklet((contribution, chunkContext) -> {
System.out.println("########## Sending Mail ########");
return RepeatStatus.FINISHED;
}).build();
}

@Bean
public Flow initCheckFlow() {
FlowBuilder<Flow> flowBuilder = new FlowBuilder<>("initCheckFlow");

flowBuilder.start(checkDB())
.next(checkService())
.end();

return flowBuilder.build();
}

@Bean
public Flow sendMailFLow() {
FlowBuilder<Flow> flowBuilder = new FlowBuilder<>("sendMailFLow");

flowBuilder.start(sendMail())
.end();

return flowBuilder.build();
}
}

Above we have created 2 flows – initCheckFlow and sendMailFLow. A Flow can contains steps or other flows. initCheckFlow flow contains 2 steps while the sendMailFLow contains 1. We created flow using FlowBuilder. Lets use these 2 flows inside a Job. JobConfiguration.java


package com.mynotes.spring.batch;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JobConfiguration {

@Autowired
private JobBuilderFactory jobBuilderFactory;

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
public Step step1() {
return stepBuilderFactory.get("step1").tasklet((contribution, chunkContext) -> {
System.out.println("########## JOB STEP 1 ########");
return RepeatStatus.FINISHED;
}).build();
}

@Bean
public Job myJob(@Qualifier("initCheckFlow") Flow initCheckFlow, @Qualifier("sendMailFLow") Flow sendMailFLow) {

return jobBuilderFactory.get("myJob")
.start(initCheckFlow)
.next(step1())
.on("COMPLETED").to(sendMailFLow)
.end()
.build();
}

}

Above we created a simple job myJob which contains 1 step. We inject our 2 flows into it and used accordingly. Output:

spring-batch-flows1

Advertisements
%d bloggers like this: