Tuesday 24 November 2020

Spring Batch: Job composition (nested jobs)

  Using Job Composition, we can compose parent job using multiple child jobs.

Follow below steps to create a parent job with

a.   Initialization Step

b.   Child Job1

c.    Child Job2

d.   Cleanup Step

 

Step 1: Create Initialization Step.

@Bean
public Step parentJobInitializer() {
	return this.stepBuilderFactory.get("Parent-Job-Initializer")
			.tasklet((StepContribution contribution, ChunkContext chunkContext) -> {

				String stepName = chunkContext.getStepContext().getStepName();

				System.out.println(stepName + " is getting executed!!!");

				return RepeatStatus.FINISHED;

			}).build();
}

 

Step 2: Create child job1.

@Bean
public Step childJob1Step1() {
	return this.stepBuilderFactory.get("ChildJob1-Step1")
			.tasklet((StepContribution contribution, ChunkContext chunkContext) -> {

				String stepName = chunkContext.getStepContext().getStepName();

				System.out.println(stepName + " is getting executed!!!");

				return RepeatStatus.FINISHED;

			}).build();
}

@Bean
public Job childJob1() {
	return jobBuilderFactory.get("childJob1").start(childJob1Step1()).build();
}

 

Step 3: Create Child Job 2.

@Bean
public Step childJob2Step1() {
	return this.stepBuilderFactory.get("ChildJob2-Step1")
			.tasklet((StepContribution contribution, ChunkContext chunkContext) -> {

				String stepName = chunkContext.getStepContext().getStepName();

				System.out.println(stepName + " is getting executed!!!");

				return RepeatStatus.FINISHED;

			}).build();
}

@Bean
public Job childJob2() {
	return jobBuilderFactory.get("childJob2").start(childJob2Step1()).build();
}

 

Step 4: Create Cleanup Step

@Bean
public Step parentJobCleanup() {
	return this.stepBuilderFactory.get("Parent-Job-Cleanup")
			.tasklet((StepContribution contribution, ChunkContext chunkContext) -> {

				String stepName = chunkContext.getStepContext().getStepName();

				System.out.println(stepName + " is getting executed!!!");

				return RepeatStatus.FINISHED;

			}).build();
}

 

Step 5: Create parent job using Initialization step, childJob1, childjob2 and cleanup step.

To make job composition possible, we need to convert the jobs to steps. You can do this using JobStepBuilder.

Step childJob1Step = new JobStepBuilder(new StepBuilder("childJob1")).job(childJob1()).launcher(jobLauncher)
.repository(jobRepository).transactionManager(platformTransactionManager).build();

Step childJob2Step = new JobStepBuilder(new StepBuilder("childJob2")).job(childJob2()).launcher(jobLauncher)
.repository(jobRepository).transactionManager(platformTransactionManager).build();

 

Once you define the Child Job steps, create parent job.

@Bean
public Job parentJob(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager) {
	Step childJob1Step = new JobStepBuilder(new StepBuilder("childJob1")).job(childJob1()).launcher(jobLauncher)
			.repository(jobRepository).transactionManager(platformTransactionManager).build();

	Step childJob2Step = new JobStepBuilder(new StepBuilder("childJob2")).job(childJob2()).launcher(jobLauncher)
			.repository(jobRepository).transactionManager(platformTransactionManager).build();

	return jobBuilderFactory.get("parentJob").start(parentJobInitializer()).next(childJob1Step).next(childJob2Step)
			.next(parentJobCleanup()).build();
}

 

Step 6: Set the jobs to be executed in application.properties by adding following property.

 

spring.batch.job.names=parentJob

Why do we add above property?

It is since Spring execute all the jobs by default, but we don’t want to execute child jobs independently. That’s why we explicitly mention spring batch to execute parentJob and ignore other jobs.

 

Find the below working application.

 

Step 1: Create new maven project ‘job-composition’.

 

Step 2: Update pom.xml with maven dependencies.

 

pom.xml

 

<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.sample.app</groupId>
	<artifactId>job-composition</artifactId>
	<version>1</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>

		<!-- https://mvnrepository.com/artifact/org.springframework.batch/spring-batch-core -->
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-core</artifactId>
		</dependency>

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

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

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
	</dependencies>
</project>

 

Step 3: Create new package ‘com.sample.app.configuration’ and define JobConfiguration.

 

JobConfiguration.java

 

package com.sample.app.configuration;

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.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.builder.JobStepBuilder;
import org.springframework.batch.core.step.builder.StepBuilder;
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;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
@EnableBatchProcessing
public class JobConfiguration {
	@Autowired
	private JobBuilderFactory jobBuilderFactory;

	@Autowired
	private StepBuilderFactory stepBuilderFactory;

	@Autowired
	private JobLauncher jobLauncher;

	@Bean
	public Step parentJobInitializer() {
		return this.stepBuilderFactory.get("Parent-Job-Initializer")
				.tasklet((StepContribution contribution, ChunkContext chunkContext) -> {

					String stepName = chunkContext.getStepContext().getStepName();

					System.out.println(stepName + " is getting executed!!!");

					return RepeatStatus.FINISHED;

				}).build();
	}

	@Bean
	public Step childJob1Step1() {
		return this.stepBuilderFactory.get("ChildJob1-Step1")
				.tasklet((StepContribution contribution, ChunkContext chunkContext) -> {

					String stepName = chunkContext.getStepContext().getStepName();

					System.out.println(stepName + " is getting executed!!!");

					return RepeatStatus.FINISHED;

				}).build();
	}

	@Bean
	public Job childJob1() {
		return jobBuilderFactory.get("childJob1").start(childJob1Step1()).build();
	}

	@Bean
	public Step childJob2Step1() {
		return this.stepBuilderFactory.get("ChildJob2-Step1")
				.tasklet((StepContribution contribution, ChunkContext chunkContext) -> {

					String stepName = chunkContext.getStepContext().getStepName();

					System.out.println(stepName + " is getting executed!!!");

					return RepeatStatus.FINISHED;

				}).build();
	}

	@Bean
	public Job childJob2() {
		return jobBuilderFactory.get("childJob2").start(childJob2Step1()).build();
	}

	@Bean
	public Step parentJobCleanup() {
		return this.stepBuilderFactory.get("Parent-Job-Cleanup")
				.tasklet((StepContribution contribution, ChunkContext chunkContext) -> {

					String stepName = chunkContext.getStepContext().getStepName();

					System.out.println(stepName + " is getting executed!!!");

					return RepeatStatus.FINISHED;

				}).build();
	}

	@Bean
	public Job parentJob(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager) {
		Step childJob1Step = new JobStepBuilder(new StepBuilder("childJob1")).job(childJob1()).launcher(jobLauncher)
				.repository(jobRepository).transactionManager(platformTransactionManager).build();

		Step childJob2Step = new JobStepBuilder(new StepBuilder("childJob2")).job(childJob2()).launcher(jobLauncher)
				.repository(jobRepository).transactionManager(platformTransactionManager).build();

		return jobBuilderFactory.get("parentJob").start(parentJobInitializer()).next(childJob1Step).next(childJob2Step)
				.next(parentJobCleanup()).build();
	}

}

 

Step 4: Define App.java

 

App.java

 

package com.sample.app;

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

@EnableBatchProcessing
@SpringBootApplication
public class App {

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

 

Step 5: Create application.properties file under src/main/resources folder.

 

application.properties

 

logging.level.root=ERROR
logging.level.org.hibernate=ERROR

## H2 specific properties
spring.h2.console.enabled=true
spring.h2.console.path=/h2

spring.datasource.url=jdbc:h2:file:~/db/myOrg.db;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;

spring.datasource.username=krishna
spring.datasource.password=password123

spring.datasource.driverClassName=org.h2.Driver

## JPA specific properties
# Creates the schema, destroying previous data.
spring.jpa.hibernate.ddl-auto=create-drop

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=false

## Database connection pooling properties
# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.max-wait=10000

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=10
spring.datasource.tomcat.max-idle=5
spring.datasource.tomcat.min-idle=3

spring.batch.job.names=parentJob

 

Total project structure looks like below.

 


Run App.java, you will see below messages in console.

 

Parent-Job-Initializer is getting executed!!!

ChildJob1-Step1 is getting executed!!!

ChildJob2-Step1 is getting executed!!!

Parent-Job-Cleanup is getting executed!!!

 

You can download complete working application from this link.

https://github.com/harikrishna553/springboot/tree/master/batch/job-composition


 

 

 

 

 

 

 

 

Previous                                                    Next                                                    Home

1 comment:

  1. Nice Sample..Thanks. Do we have similar sample for chunk also

    ReplyDelete