Spring Batch – Job Parameters

In this post we will see how to start a job using jobParameters. Lets see what the concern/limitation with the previous approach. Below is our tipical spring batch application. 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-jobParameters</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>batch-jobParameters</name>
<description>Demo project for Spring Boot batch-jobParameters</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&useSSL=false
spring.datasource.username=root
spring.datasource.password=admin

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

Our job class JobConfiguration.java


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(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("################ MyJob - STEP 1");
return RepeatStatus.FINISHED;
}
}).build();
}

@Bean
public Job helloWorldJob() {
return jobBuilderFactory.get("job1")
.start(step1())
.build();
}

}

Above we have a simple tasklet step that is added to our job job1.  Lets run it

spring-batch-jobparameters1

Lets run it again :

spring-batch-jobparameters2

We have 2 main problems here:

  • Spring batch by default run all the jobs it finds in its context. Most of the time we want it to run when we trigger it somehow.
  • A Job instance cannot be re-run. We cannot chage the job name again and again wo re-run. We could use .incrementer(new RunIdIncrementer()) in our job building but this would mean that every trigger will start the job from the begining. What if we had a job that stopped in between for some reason and we fixed the issue and want to resume the job from that point.

Lets start with the first issue. To stop spring running job on startup we have to add following properties in application.properties


spring.batch.job.enabled=false

Now lets add a simple Rest service to initiate our job.  For that we have wo add the web starter pack in our pom.xml


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Our rest controller JobLaunchController.java


package com.mynotes.spring.batch;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class JobLaunchController {

@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;

@RequestMapping(value = "/launch", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.ACCEPTED)
public void launch(@RequestParam("param") String param) throws Exception {
JobParameters jobParameters =
new JobParametersBuilder()
.addString("param",param)
.toJobParameters();

this.jobLauncher.run(job, jobParameters);

}

}

Spring batch provides a JobLauncher out-of-box which you can use to launch your job. To handle complex use case you need a JobOperator but lets for now stick to the JobLauncher. So above we autowired JobLauncher and our Job job1. We wrote a simple method with ‘/launch’ mapping and ceated the JobParameters object and passed it to our job.

Lets run it. I will be using postman to hit this url . you can choose any of your own. Lets start the application.:

spring-batch-jobparameters3

Notice that the job didnt executed on startup. Let hit a POST request on http://localhost:8080/launch?param=try1

spring-batch-jobparameters4

Lets launch again whing the same param=try1

spring-batch-jobparameters5

Lets try with another param – http://localhost:8080/launch?param=try2 and now it will run.

Lets take a look how to capture this parameters in our step. Changing 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.configuration.annotation.StepScope;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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
@StepScope
public Tasklet myTasklet(@Value("#{jobParameters['param']}") String param) {
return (stepContribution, chunkContext) -> {
System.out.println("################"+param);
System.out.println("################ MyJob - STEP 1");
return RepeatStatus.FINISHED;
};
}

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

@Bean
public Job helloWorldJob() {
return jobBuilderFactory.get("MyJob")
.start(step1())
.build();
}

}

Above we created a seperate Tasklet method myTasklet. Notice that it has an annotaion @StepScope. This tells spring to instantiate this bean when the step that is using it calls it. In other words this is lazly instanciated (Other typical spring beans are created on startup) .  So on load of the beans at startup a proxy is used to satify any dependencies and when this methos is called, then this bean is created. We used @Value(“#{jobParameters[‘param’]}”) to capture the jobparameters. Running our App with a POST request – http://localhost:8080/launch?param=passing1

spring-batch-jobparameters6

So we saw how job parameters are use to initate and identify a job instance. We can do a restart by passing the same job parameters and spring will start from where it left off. How it is done , we will see in future posts.

Advertisements
%d bloggers like this: