Enterprise Java

Java Batch Tutorial

1. Introduction

In this post, we feature a comprehensive Example on Java Batch. Java batch application is a Java application which processes a group of records, usually large volumes of data, as a single unit automatically. Spring introduced the Spring-batch framework in 2006. Spring Batch is an open source framework designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. It has supported JSR 352 since version 3.0. In this example, I will demonstrate how to create a Java batch application in 15 minutes utilizing Spring Batch framework.

2. Technologies used

The example code in this article was built and run using:

  • Java 1.8.101 (1.8.x will do fine)
  • Maven 3.3.9 (3.3.x will do fine)
  • Spring boot 1.5.14 ( Higher version will do fine)
  • Spring Batch 3.0.5.RELEASE (4.0.0.M1 will do fine)
  • Eclipse Mars (Any Java IDE would work)

3. Spring Boot Batch Application

The easiest way to generate a Spring-boot batch application is via the Spring starter tool with the steps below:

  1. Go to https://start.spring.io/.
  2. Select Maven Project with Java and Spring Boot version 1.5.14 and type in Batch, H2 in the “search for dependencies” bar.
  3. Enter the group name as jcg.demo.zheng and artifact as springboot-batch-demo.
  4. Click the Generate Project button.

A zip file – springboot-batch-demo.zip will be generated and downloaded to your workstation.

4. Eclipse Maven Project

In this step, I will import the generated Spring boot batch project into my Eclipse workspace with these steps:

  1. Unzip the springboot-batch-demo.zip to C:\MZheng_Java_workspace\spingboot-batch-demo.
  2. Launch Eclipse.
  3. Click Import->Existing Maven Project.
  4. Browse to the C:\MZheng_Java_workspace\spingboot-batch-demo and click Finish.

A maven project – springboot-batch-demo will be imported.

4.1 Maven Dependency

The generated pom.xml includes spring-boot-starter-batch and H2database. There is no modification needed in this example.

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>jcg.demo.zheng</groupId>
	<artifactId>springboot-batch-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springboot-batch-demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.14.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>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

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


</project>

4.2 Spring Batch Application

The generated SpringbootBatchDemoApplication.java is annotated with @SpringBootApplication. It is equivalent to using @Configuration@EnableAutoConfiguration, and @ComponentScanwith their default attributes.

In this step, I will add @EnableBatchProcessingto enable Batch auto-configuration. By default, it executes all Jobs in the application context on startup.

SpringbootBatchDemoApplication.java

package jcg.demo.zheng.springbootbatchdemo;

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

@SpringBootApplication
@EnableBatchProcessing
public class SpringbootBatchDemoApplication {

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

5. Spring Batch Framework Overview

Spring batch framework defines a batch domain language. In this example, we will stick to these terms to avoid confusion. Please click here for more details.

Java Batch - Anatomy of a Spring Batch Job
Fig. 1: Anatomy of a Spring Batch Job

A simplified architecture diagram above shows the key components. The blue ones are Spring Batch Beans; the yellow ones are Spring Batch interfaces. Developers implement the ItemReader, ItemWriter, and ItemProcessor interfaces based on the business processing logic. Then they wire them into batch Jobs and Steps. These batch jobs then will be executed by the Spring JobLauncher.

5.1 Batch Steps

According to the Batch Domain language, a Step is a domain object that encapsulates an independent, sequential phase of a batch job. A Step contains all of the information necessary to define and control the actual batch processing. In this step, I will create a Reader-Processor-Writer step which reads messages, converts them into an uppercase format, and then outputs them to the console.

5.1.1 SimpleReader

SimpleReader is a class which implements the ItemReader. It reads a list of String messages.

SimpleReader.java

package jcg.demo.zheng.springbootbatchdemo.step;

import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;

public class SimpleReader implements ItemReader {

	private String[] tenMessages = { "Message 1", "Message 2", "Message 3", "Message 4", "Message 5", "Message 6",
			"Message 7", "Message 8", "Message 9", "Message 10" };

	private int count = 0;

	@Override
	public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {

		if (count < tenMessages.length) {
			return tenMessages[count++];
		} else {
			count = 0;
		}
		return null;
	}

}

5.1.2 SimpleWriter

SimpleWriter is a class which implements the ItemWriter. It writes out String message.

SimpleWriter.java

package jcg.demo.zheng.springbootbatchdemo.step;

import java.util.List;

import org.springframework.batch.item.ItemWriter;

public class SimpleWriter implements ItemWriter {

	@Override
	public void write(List<? extends String> messages) throws Exception {
		for (String msg : messages) {
			System.out.println("Writing the data " + msg);
		}
	}

}

5.1.3 SimpleProcessor

SimpleProcessor is a class which implements the ItemProcessor. It converts the String message to the uppercase format.

SimpleProcessor.java

package jcg.demo.zheng.springbootbatchdemo.step;

import org.springframework.batch.item.ItemProcessor;

public class SimpleProcessor implements ItemProcessor<String, String> {

	@Override
	public String process(String data) throws Exception {
		System.out.println("process for " + data);
		return data.toUpperCase();
	}

}

5.2 Batch Job Execution Listener

A JobExecution refers to the technical concept of a single attempt to run a Job. An execution may end in either failure or success. In this step, I will create a listener to output the job status after the job is executed.

JobCompletionListener.java

package jcg.demo.zheng.springbootbatchdemo.listener;

import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;
import org.springframework.stereotype.Component;

@Component
public class JobCompletionListener extends JobExecutionListenerSupport {

	@Override
	public void afterJob(JobExecution jobExecution) {

		StringBuilder msg = new StringBuilder();
		msg.append("##### Finishing Job Name=").append(jobExecution.getJobInstance().getJobName())
				.append(" JOB_EXE_ID=").append(jobExecution.getId()).append(" JOB_ID=").append(jobExecution.getJobId())
				.append(", Status=").append(jobExecution.getStatus()).append(", StartTime=")
				.append(jobExecution.getStartTime()).append(", EndTime=").append(jobExecution.getEndTime());
		System.out.println(msg);
	}

}

5.3 Batch Job and Step Configuration

5.3.1 SimpleAdapterReader

SimpleAdapterReader is a class which implements the InitializingBean.

SimpleAdapterReader.java

package jcg.demo.zheng.springbootbatchdemo.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class SimpleAdapterReader implements InitializingBean {

	private List messages = new ArrayList();

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("User bean initialized successfully...");
		loadItems();
	}

	private void loadItems() throws Exception {
		for (int i = 0; i  0) {
			return messages.remove(0);
		} else {
			System.out.println("No more item to read");
			return null;
		}
	}

}

A Job is an entity that encapsulates the entire batch process.

In this step, I will configure two Spring batch jobs – simpleJob and  simpleAdapterJob:

  • Wire simpleJob with oneStep which consists of SimpleReader, SimpleWriter, and SimpleProcessor with chunk sizes of three.
  • Wire simpleAdapterJob with adapterStep which contains simpleAdaperReader, SimpleWriter, and SimpleProcessor with chunk sizes of three.

BatchConfig.java

package jcg.demo.zheng.springbootbatchdemo.config;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecutionListener;
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.launch.support.RunIdIncrementer;
import org.springframework.batch.item.adapter.ItemReaderAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import jcg.demo.zheng.springbootbatchdemo.step.SimpleProcessor;
import jcg.demo.zheng.springbootbatchdemo.step.SimpleReader;
import jcg.demo.zheng.springbootbatchdemo.step.SimpleWriter;

@Configuration
public class BatchConfig {

	@Autowired
	public JobBuilderFactory jobBuilderFactory;

	@Autowired
	public StepBuilderFactory stepBuilderFactory;

	@Autowired
	public JobExecutionListener listener;

	@Autowired
	public SimpleAdapterReader simpleAdapter;

	private int STEP_CHUNK_SIZE = 3;

	private static final String SIMPLE_JOB_NAME = "simpleJob";
	private static final String STEP_ONE_NAME = "oneStep";
	private static final String STEP_A_NAME = "adapterStep";

	@Bean
	public Job simpleJob() {
		return jobBuilderFactory.get(SIMPLE_JOB_NAME).incrementer(new RunIdIncrementer()).listener(listener)
				.flow(oneStep()).end().build();
	}

	@Bean
	public Job simpleAdapterJob() {
		return jobBuilderFactory.get("simpleAdapterJob").incrementer(new RunIdIncrementer()).listener(listener)
				.flow(adapterStep()).end().build();
	}

	@Bean
	public Step oneStep() {
		return stepBuilderFactory.get(STEP_ONE_NAME). chunk(STEP_CHUNK_SIZE).reader(new SimpleReader())
				.processor(new SimpleProcessor()).writer(new SimpleWriter()).build();
	}

	@Bean
	public Step adapterStep() {
		return stepBuilderFactory.get(STEP_A_NAME). chunk(STEP_CHUNK_SIZE).reader(simpleAdaperReader())
				.processor(new SimpleProcessor()).writer(new SimpleWriter()).build();
	}

	@Bean
	public ItemReaderAdapter simpleAdaperReader() {
		ItemReaderAdapter adapter = new ItemReaderAdapter();
		adapter.setTargetObject(simpleAdapter);
		adapter.setTargetMethod("nextItem");
		return adapter;
	}
}

6. Demo

Start the SpringbootBatchDemoApplication, we will see that both Batch jobs are executed.

SpringbootBatchDemoApplication Output

 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v1.5.14.RELEASE)

2018-06-20 17:10:18.570  INFO 11712 --- [           main] j.d.z.s.SpringbootBatchDemoApplication   : Starting SpringbootBatchDemoApplication on SL2LS431841 with PID 11712 (C:\MZheng_Java_workspace\springboot-batch-demo\target\classes started by Shu.Shan in C:\MZheng_Java_workspace\springboot-batch-demo)
2018-06-20 17:10:18.573  INFO 11712 --- [           main] j.d.z.s.SpringbootBatchDemoApplication   : No active profile set, falling back to default profiles: default
2018-06-20 17:10:18.675  INFO 11712 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27c6e487: startup date [Wed Jun 20 17:10:18 CDT 2018]; root of context hierarchy
User bean initialized successfully...
2018-06-20 17:10:21.054  INFO 11712 --- [           main] o.s.jdbc.datasource.init.ScriptUtils     : Executing SQL script from class path resource [org/springframework/batch/core/schema-h2.sql]
2018-06-20 17:10:21.139  INFO 11712 --- [           main] o.s.jdbc.datasource.init.ScriptUtils     : Executed SQL script from class path resource [org/springframework/batch/core/schema-h2.sql] in 83 ms.
2018-06-20 17:10:21.416  INFO 11712 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-06-20 17:10:21.443  INFO 11712 --- [           main] o.s.b.a.b.JobLauncherCommandLineRunner   : Running default command line with: []
2018-06-20 17:10:21.461  INFO 11712 --- [           main] o.s.b.c.r.s.JobRepositoryFactoryBean     : No database type set, using meta data indicating: H2
2018-06-20 17:10:21.820  INFO 11712 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : No TaskExecutor has been set, defaulting to synchronous executor.
2018-06-20 17:10:21.975  INFO 11712 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=simpleJob]] launched with the following parameters: [{run.id=1}]
2018-06-20 17:10:22.023  INFO 11712 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [oneStep]
process for Message 1
process for Message 2
process for Message 3
Writing the data MESSAGE 1
Writing the data MESSAGE 2
Writing the data MESSAGE 3
process for Message 4
process for Message 5
process for Message 6
Writing the data MESSAGE 4
Writing the data MESSAGE 5
Writing the data MESSAGE 6
process for Message 7
process for Message 8
process for Message 9
Writing the data MESSAGE 7
Writing the data MESSAGE 8
Writing the data MESSAGE 9
process for Message 10
Writing the data MESSAGE 10
##### Finishing Job Name=simpleJob JOB_EXE_ID=1 JOB_ID=1, Status=COMPLETED, StartTime=Wed Jun 20 17:10:21 CDT 2018, EndTime=Wed Jun 20 17:10:22 CDT 2018
2018-06-20 17:10:22.083  INFO 11712 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=simpleJob]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED]
2018-06-20 17:10:22.095  INFO 11712 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=simpleAdapterJob]] launched with the following parameters: [{run.id=1}]
2018-06-20 17:10:22.108  INFO 11712 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [adapterStep]
process for SimpleAdatperReader Message 0
process for SimpleAdatperReader Message 1
process for SimpleAdatperReader Message 2
Writing the data SIMPLEADATPERREADER MESSAGE 0
Writing the data SIMPLEADATPERREADER MESSAGE 1
Writing the data SIMPLEADATPERREADER MESSAGE 2
process for SimpleAdatperReader Message 3
process for SimpleAdatperReader Message 4
process for SimpleAdatperReader Message 5
Writing the data SIMPLEADATPERREADER MESSAGE 3
Writing the data SIMPLEADATPERREADER MESSAGE 4
Writing the data SIMPLEADATPERREADER MESSAGE 5
process for SimpleAdatperReader Message 6
process for SimpleAdatperReader Message 7
process for SimpleAdatperReader Message 8
Writing the data SIMPLEADATPERREADER MESSAGE 6
Writing the data SIMPLEADATPERREADER MESSAGE 7
Writing the data SIMPLEADATPERREADER MESSAGE 8
process for SimpleAdatperReader Message 9
process for SimpleAdatperReader Message 10
process for SimpleAdatperReader Message 11
Writing the data SIMPLEADATPERREADER MESSAGE 9
Writing the data SIMPLEADATPERREADER MESSAGE 10
Writing the data SIMPLEADATPERREADER MESSAGE 11
process for SimpleAdatperReader Message 12
process for SimpleAdatperReader Message 13
process for SimpleAdatperReader Message 14
Writing the data SIMPLEADATPERREADER MESSAGE 12
Writing the data SIMPLEADATPERREADER MESSAGE 13
Writing the data SIMPLEADATPERREADER MESSAGE 14
process for SimpleAdatperReader Message 15
process for SimpleAdatperReader Message 16
process for SimpleAdatperReader Message 17
Writing the data SIMPLEADATPERREADER MESSAGE 15
Writing the data SIMPLEADATPERREADER MESSAGE 16
Writing the data SIMPLEADATPERREADER MESSAGE 17
No more item to read
process for SimpleAdatperReader Message 18
process for SimpleAdatperReader Message 19
Writing the data SIMPLEADATPERREADER MESSAGE 18
Writing the data SIMPLEADATPERREADER MESSAGE 19
##### Finishing Job Name=simpleAdapterJob JOB_EXE_ID=2 JOB_ID=2, Status=COMPLETED, StartTime=Wed Jun 20 17:10:22 CDT 2018, EndTime=Wed Jun 20 17:10:22 CDT 2018
2018-06-20 17:10:22.148  INFO 11712 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=simpleAdapterJob]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED]
2018-06-20 17:10:22.150  INFO 11712 --- [           main] j.d.z.s.SpringbootBatchDemoApplication   : Started SpringbootBatchDemoApplication in 4.066 seconds (JVM running for 5.163)
2018-06-20 17:10:22.150  INFO 11712 --- [       Thread-3] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@27c6e487: startup date [Wed Jun 20 17:10:18 CDT 2018]; root of context hierarchy
2018-06-20 17:10:22.153  INFO 11712 --- [       Thread-3] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

7. Summary

In this example, we demonstrated how to build a Java batch application with Spring Batch framework in five steps:

  1. Generate the Spring boot batch application.
  2. Import the generated project into Eclipse IDE.
  3. Create implementation classes for Spring Batch ItemReader, ItemWriter, and ItemProcessor Interfaces.
  4. Configure the Spring batch Job and Step with Java based configuration.
  5. Start the Spring boot application to execute Spring batch jobs.

8. Download the Source Code

This example consists of a Java Batch application.

Download
You can download the full source code of this example here: springboot-batch-demo

Mary Zheng

Mary has graduated from Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She works as a senior Software Engineer in the telecommunications sector where she acts as a leader and works with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Partha
Partha
5 years ago

SimpleWriter, SimpleAdapterReader classes not matching the code.

Back to top button