Today, I show you how to create a Spring Batch Tasklet with Spring Boot Example. This post introduces very clear to help you understand then you can create your own example. You know what, Spring Batch provides reusable functions that are essential in processing large volumes of records, including logging/tracing, transaction management, job processing statistics, job restart, skip, and resource management. Let’s dive into it.
Spring Batch Tasklet with Spring Boot
Project Setup
Tools and framework that we use:
Spring Boot 2.2.2 RELEASE
Spring Batch 4.2.1
Maven 3.6
Java 8
Project Directory
Our project will have a structure like below
Project Dependencies
We’re using the dependencies like below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
... <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.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>8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency> </dependencies> ... |
Creating the Model
Here, we assume that we need to read employee information from CSV files named employees1.csv and employees2.csv, its format looks like below:
1 2 |
David, Pham, Java deparment James, Bon, PHP deparment |
Note: This file is located in the project folder/resources/csv/
Then, we create a POJO class that its object map with data in the above CSV file
1 2 3 4 5 6 7 8 9 10 11 |
package com.javabycode.model; public class Employee { private String firstName; private String lastName; private String department; public Employee() { } // getter and setter } |
Configuring Spring Batch Jobs
We assume that we have the data CSV files that contain a list of employees, its format looks like below.
Create BatchConfig class and add the @EnableBatchProcessing annotation for this class to get the support of Spring Batch features.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.javabycode.batch; import javax.sql.DataSource; import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.context.annotation.Configuration; @Configuration @EnableBatchProcessing public class BatchConfig extends DefaultBatchConfigurer { @Override public void setDataSource(DataSource dataSource) { // initialize a Map based JobRepository by default } } |
Create Spring Batch job that named filterJob
for our own Spring Batch Tasklet application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
package com.javabycode.batch; import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.item.file.FlatFileItemReader; import org.springframework.batch.item.file.FlatFileItemWriter; import org.springframework.batch.item.file.MultiResourceItemReader; import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder; import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder; import org.springframework.batch.item.file.builder.MultiResourceItemReaderBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import com.javabycode.model.Employee; @Configuration public class EmployeeFilterJobConfig { private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeFilterJobConfig.class); @Bean public Job filterEmployeesJob(JobBuilderFactory jobBuilders, StepBuilderFactory stepBuilders) { return jobBuilders.get("filterJob") .start(filterEmployeesStep(stepBuilders)) .next(deleteFilesStep(stepBuilders)).build(); } @Bean public Step filterEmployeesStep(StepBuilderFactory stepBuilders) { return stepBuilders.get("filterStep") .<Employee, Employee>chunk(10).reader(multiItemReader()) .processor(itemProcessor()).writer(itemWriter()).build(); } @Bean public Step deleteFilesStep(StepBuilderFactory stepBuilders) { return stepBuilders.get("deleteFilesStep") .tasklet(fileDeletingTasklet()).build(); } @Bean public MultiResourceItemReader<Employee> multiItemReader() { ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver(); Resource[] resources = null; try { resources = patternResolver .getResources("file:target/test-inputs/*.csv"); } catch (IOException e) { LOGGER.error("error reading files", e); } return new MultiResourceItemReaderBuilder<Employee>() .name("multiemployeeItemReader").delegate(itemReader()) .resources(resources).setStrict(true).build(); } @Bean public FlatFileItemReader<Employee> itemReader() { return new FlatFileItemReaderBuilder<Employee>() .name("employeeItemReader").delimited() .names(new String[] {"firstName", "lastName", "department"}) .targetType(Employee.class).build(); } @Bean public EmployeeItemProcessor itemProcessor() { return new EmployeeItemProcessor(); } @Bean public FlatFileItemWriter<Employee> itemWriter() { return new FlatFileItemWriterBuilder<Employee>() .name("employeeItemWriter") .resource(new FileSystemResource( "target/test-outputs/employees.txt")) .delimited().delimiter(", ") .names(new String[] {"firstName", "lastName", "department"}).build(); } @Bean public FileDeletingTasklet fileDeletingTasklet() { return new FileDeletingTasklet( new FileSystemResource("target/test-inputs")); } } |
Let’s dive into the above code:
The method filterJob
returns an instance of Job
bean. That Job
bean may be likely understood is a container of steps and other configuration options.
The methodfilterStep
returns an instance of Step
bean. This bean encapsulates phases of a batch job. Here, we have a step named filterStep
that takes care of reading and writing the CSV file.
We use the FlatFileItemReader to read the CSV file, its instance is created by the FlatFileItemReaderBuilder implementation. In order to FlatFileItemReader processes your CSV file, you must provide the information of file format, e.g: java mapping class, matched fields.
The method multiItemReader
reads multiple input files.
The EmployeeItemProcessor
handles the processing of the data.
To write file we use FlatFileItemWriter that its instance created by a FlatFileItemWriterBuilder
builder implementation.
We use the FileDeletingTasklet
bean to clean the input folder test-inputs
.
Processing the Data
In most cases, we need to make some data processing from our original data. So we can create EmployeeItemProcessor
that implements ItemProcessor
interface to take care of that task. Let’s have a look at below implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.javabycode.batch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.batch.item.ItemProcessor; import com.javabycode.model.Employee; public class EmployeeItemProcessor implements ItemProcessor<Employee, Employee> { private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeItemProcessor.class); @Override public Employee process(Employee employee) throws Exception { Employee result = new Employee(); if (employee.getDepartment().contains("Java")) { result.setFirstName(employee.getFirstName()); result.setLastName(employee.getLastName()); result.setDepartment(employee.getDepartment()); LOGGER.info("converting '{}' into '{}'", employee, result); return result; }else return null; } } |
Creating a Spring Batch Tasklet
In Spring batch, the Tasklet
is an interface, which will be called to perform a single task only, like clean or set up resources before or after any step execution. Here, we create a tasklet that will delete all files a directory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package com.javabycode.batch; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.UnexpectedJobExecutionException; 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.core.io.Resource; public class FileDeletingTasklet implements Tasklet { private static final Logger LOGGER = LoggerFactory.getLogger(FileDeletingTasklet.class); private Resource directory; public FileDeletingTasklet(Resource directory) { this.directory = directory; } @Override public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) { try (Stream<Path> walk = Files.walk(Paths.get(directory.getFile().getPath()))) { walk.filter(Files::isRegularFile).map(Path::toFile) .forEach(File::delete); } catch (IOException e) { LOGGER.error("error deleting files", e); throw new UnexpectedJobExecutionException( "unable to delete files"); } return RepeatStatus.FINISHED; } } |
Configuring SpringBootApplication class
To configure Spring Boot application we create simple class below. If you haven’t had knowledge about Spring Boot yet. Let’s checkout the post Spring Boot Tutorial for Beginners
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.javabycode; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @SpringBootApplication( exclude = {DataSourceAutoConfiguration.class}) public class MySpringBootApplication { public static void main(String[] args) { SpringApplication.run(MySpringBootApplication.class, args); } } |
So far, we have finished for implement our Spring Batch Tasklet with Spring Boot Example.
Running Application
To demo our Spring Batch Tasklet with Spring Boot Example, we run the below command in the console.
1 |
mvn spring-boot:run |
Note: You must make sure your CSV files already stored in the input folder test-inputs
.
While running that command, you will see the output like below
That’s all about Spring Batch Tasklet with Spring Boot.
If you find this post useful, don't hesitate to share it with your friends or other people for growing knowledge together and is an effort to contribute us.
References
Spring Boot Tutorial for Beginners
Spring Batch Frameworks