Here I am going to show a simple Spring Boot Batch example, which will read data from csv file and write into a database using JdbcTemplate.
Spring Boot Batch CSV to Database:
1. Technologies:
- Spring Boot 2.1.3
- Spring Batch 2.1.3
- MySql
- JDBC Template
- Java 8
2. Project Structure
Spring Boot Batch Example:
3 Dependencies:
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.onlinetutorialspoint</groupId>
<artifactId>SpringBoot-Batch-Example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringBoot-Batch-Example</name>
<description>Spring Boot Batch CSV to Database Example</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4. Preparing Configurations:
4.1 Database Schema:
CREATE TABLE employee (
first_name VARCHAR(40),
last_name VARCHAR(40),
company_name VARCHAR(40),
address VARCHAR(40),
city VARCHAR(40),
county VARCHAR(40),
state VARCHAR(40),
zip VARCHAR(10)
);
4.2 Sample employee.csv data:
first_name,last_name,company_name,address,city,county,state,zip
James,Butt,"Benton, John B Jr",6649 N Blue Gum St,New Orleans,Orleans,LA,70116
Josephine,Darakjy,"Chanay, Jeffrey A Esq",4 B Blue Ridge Blvd,Brighton,Livingston,MI,48116
Art,Venere,"Chemel, James L Cpa",8 W Cerritos Ave #54,Bridgeport,Gloucester,NJ,8014
Lenna,Paprocki,Feltz Printing Service,639 Main St,Anchorage,Anchorage,AK,99501
Donette,Foller,Printing Dimensions,34 Center St,Hamilton,Butler,OH,45011
Simona,Morasca,"Chapman, Ross E Esq",3 Mcauley Dr,Ashland,Ashland,OH,44805
..........
..........
5. Application properties:
spring.datasource.url=jdbc:mysql://localhost:3306/otp
spring.datasource.username=root
spring.datasource.password=12345
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.initialize=true
spring.datasource.schema=classpath:schema.sql
spring.batch.initialize-schema=ALWAYS
6. Transfer Objects:
Employee.java – representing CSV file data and EmployeeDTO.java – representing a database table.
6.1 Employee.java
package com.onlinetutorialspoint.model;
import java.io.Serializable;
public class Employee implements Serializable {
private String firstName;
private String lastName;
private String companyName;
private String address;
private String city;
private String county;
private String state;
private String zip;
public Employee() {
}
public Employee(String firstName, String lastName, String companyName, String address, String city, String county, String state, String zip) {
this.firstName = firstName;
this.lastName = lastName;
this.companyName = companyName;
this.address = address;
this.city = city;
this.county = county;
this.state = state;
this.zip = zip;
}
// getters() and setters()
}
6.2 EmployeeDTO.java
package com.onlinetutorialspoint.model;
public class EmployeeDTO {
private String firstName;
private String lastName;
private String companyName;
private String address;
private String city;
private String county;
private String state;
private String zip;
public EmployeeDTO() {
}
public EmployeeDTO(String firstName, String lastName, String companyName, String address, String city, String county, String state, String zip) {
this.firstName = firstName;
this.lastName = lastName;
this.companyName = companyName;
this.address = address;
this.city = city;
this.county = county;
this.state = state;
this.zip = zip;
}
// getters() and setters()
}
7. Spring Batch Configuration:
Java-based spring boot batch configuration class.
FlatFileItemReader – Reads lines from input setResource(Resource r) for our case input file (employee.csv) is reading from classpath. And Line mapped with an item using setLineMapper(LineMapper).
JdbcBatchItemWriter – It implements the ItemWriter interface: uses the batching features from NamedParameterJdbcTemplate to execute a batch of statements for all items provided. We must provide an SQL query and a special callback in the form of either ItemPreparedStatementSetter or ItemSqlParameterSourceProvider.
JobBuilderFactory – Used to building jobs of various kinds.
StepBuilderFactory – Entry point for building all kinds of steps.
package com.onlinetutorialspoint.config;
import com.onlinetutorialspoint.listener.JobListener;
import com.onlinetutorialspoint.model.Employee;
import com.onlinetutorialspoint.model.EmployeeDTO;
import com.onlinetutorialspoint.processor.EmployeeProcessor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
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.support.RunIdIncrementer;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import javax.sql.DataSource;
@Configuration
@EnableBatchProcessing
public class SpringBatchConfig {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
@Autowired
public DataSource dataSource;
@Bean
public FlatFileItemReader<Employee> reader() {
FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();
reader.setResource(new ClassPathResource("employee.csv"));
reader.setLineMapper(new DefaultLineMapper<Employee>() {{
setLineTokenizer(new DelimitedLineTokenizer() {{
setNames(new String[] { "first_name", "last_name","company_name","address","city","county","state","zip" });
}});
setFieldSetMapper(new BeanWrapperFieldSetMapper() {{
setTargetType(Employee.class);
}});
}});
return reader;
}
@Bean
public EmployeeProcessor processor() {
return new EmployeeProcessor();
}
@Bean
public JdbcBatchItemWriter<EmployeeDTO> writer() {
JdbcBatchItemWriter<EmployeeDTO> writer = new JdbcBatchItemWriter<EmployeeDTO>();
writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
writer.setSql("INSERT INTO employee (first_name,last_name,company_name,address,city,county,state,zip) " +
"VALUES (:firstName, :lastName,:companyName,:address,:city,:county,:state,:zip)");
writer.setDataSource(dataSource);
return writer;
}
@Bean
public Job importUserJob(JobListener listener) {
return jobBuilderFactory.get("importUserJob")
.incrementer(new RunIdIncrementer())
.listener(listener)
.flow(step1())
.end()
.build();
}
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.<Employee, EmployeeDTO> chunk(10)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
}
8. Spring batch ItemProcessor:
Processing the Batch item based on the chunk size: For our case reading employee data from employee object and transforming it into employee dto object. Typical business logic goes here! based on your requirement.
package com.onlinetutorialspoint.processor;
import com.onlinetutorialspoint.model.Employee;
import com.onlinetutorialspoint.model.EmployeeDTO;
import org.springframework.batch.item.ItemProcessor;
public class EmployeeProcessor implements ItemProcessor<Employee, EmployeeDTO> {
@Override
public EmployeeDTO process(final Employee employee) throws Exception {
System.out.println("Transforming Employee(s) to EmployeeDTO(s)..");
final EmployeeDTO empployeeDto = new EmployeeDTO(employee.getFirstName(), employee.getLastName(),
employee.getCompanyName(), employee.getAddress(),employee.getCity(),employee.getCounty(),employee.getState()
,employee.getZip());
return empployeeDto;
}
}
9. Spring Batch Listener:
JobExecutionListenerSupport: Provides callbacks at specific points in the lifecycle of a Job when before and after job execution. afterjob() Callback after completion of a job. Called after both successful and failed executions.
To perform logic on a particular status, use “if (jobExecution.getStatus() == BatchStatus.X)“.
package com.onlinetutorialspoint.listener;
import com.onlinetutorialspoint.model.EmployeeDTO;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class JobListener extends JobExecutionListenerSupport {
private final JdbcTemplate jdbcTemplate;
@Autowired
public JobListener(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void afterJob(JobExecution jobExecution) {
if(jobExecution.getStatus() == BatchStatus.COMPLETED) {
System.out.println("In Completion Listener ..");
List<EmployeeDTO> results = jdbcTemplate.query("SELECT first_name,last_name,company_name,address,city,county,state,zip FROM employee",
(rs,rowNum)->{
return new EmployeeDTO(rs.getString(1), rs.getString(2),rs.getString(3),rs.getString(4),
rs.getString(5),rs.getString(6),rs.getString(7),rs.getString(8));
}
);
results.forEach(System.out::println);
}
}
}
10. Spring Boot Main:
package com.onlinetutorialspoint;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootBatchExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootBatchExampleApplication.class, args);
}
}
11. Build the Application:
cgoka@work:~/Documents/Work/Spring_Examples/SpringBoot-Batch-Example$ mvn clean install
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.google.inject.internal.cglib.core.$ReflectUtils$1 (file:/usr/share/maven/lib/guice.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of com.google.inject.internal.cglib.core.$ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building SpringBoot-Batch-Example 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ SpringBoot-Batch-Example ---
[INFO] Deleting /home/cgoka/Documents/Work/Spring_Examples/SpringBoot-Batch-Example/target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ SpringBoot-Batch-Example ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ SpringBoot-Batch-Example ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files to /home/cgoka/Documents/Work/Spring_Examples/SpringBoot-Batch-Example/target/classes
.........
.........
12. Run the Application:
cgoka@work:~/Documents/Work/Spring_Examples/SpringBoot-Batch-Example$ mvn spring-boot:run
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.google.inject.internal.cglib.core.$ReflectUtils$1 (file:/usr/share/maven/lib/guice.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of com.google.inject.internal.cglib.core.$ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building SpringBoot-Batch-Example 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.1.3.RELEASE:run (default-cli) > test-compile @ SpringBoot-Batch-Example >>>
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ SpringBoot-Batch-Example ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ SpringBoot-Batch-Example ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 6 source files to /home/cgoka/Documents/Work/Spring_Examples/SpringBoot-Batch-Example/target/classes
[INFO] /home/cgoka/Documents/Work/Spring_Examples/SpringBoot-Batch-Example/src/main/java/com/onlinetutorialspoint/config/SpringBatchConfig.java: /home/cgoka/Documents/Work/Spring_Examples/SpringBoot-Batch-Example/src/main/java/com/onlinetutorialspoint/config/SpringBatchConfig.java uses unchecked or unsafe operations.
[INFO] /home/cgoka/Documents/Work/Spring_Examples/SpringBoot-Batch-Example/src/main/java/com/onlinetutorialspoint/config/SpringBatchConfig.java: Recompile with -Xlint:unchecked for details.
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ SpringBoot-Batch-Example ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/cgoka/Documents/Work/Spring_Examples/SpringBoot-Batch-Example/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ SpringBoot-Batch-Example ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] <<< spring-boot-maven-plugin:2.1.3.RELEASE:run (default-cli) < test-compile @ SpringBoot-Batch-Example <<<
[INFO]
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.3.RELEASE:run (default-cli) @ SpringBoot-Batch-Example ---
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.3.RELEASE)
2019-03-20 02:55:12.956 INFO 30969 --- [ main] c.o.SpringBootBatchExampleApplication : Starting SpringBootBatchExampleApplication on work with PID 30969 (/home/cgoka/Documents/Work/Spring_Examples/SpringBoot-Batch-Example/target/classes started by cgoka in /home/cgoka/Documents/Work/Spring_Examples/SpringBoot-Batch-Example)
2019-03-20 02:55:12.961 INFO 30969 --- [ main] c.o.SpringBootBatchExampleApplication : No active profile set, falling back to default profiles: default
2019-03-20 02:55:14.298 INFO 30969 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
Wed Mar 20 02:55:14 IST 2019 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
2019-03-20 02:55:14.693 INFO 30969 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
.....
.....
2019-03-20 02:55:16.287 INFO 30969 --- [ main] o.s.b.a.b.JobLauncherCommandLineRunner : Running default command line with: []
2019-03-20 02:55:16.611 INFO 30969 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=importUserJob]] launched with the following parameters: [{run.id=7}]
2019-03-20 02:55:16.788 INFO 30969 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
Transforming Employee(s) to EmployeeDTO(s)..
Transforming Employee(s) to EmployeeDTO(s)..
Transforming Employee(s) to EmployeeDTO(s)..
Transforming Employee(s) to EmployeeDTO(s)..
Transforming Employee(s) to EmployeeDTO(s)..
Transforming Employee(s) to EmployeeDTO(s)..
Transforming Employee(s) to EmployeeDTO(s)..
Transforming Employee(s) to EmployeeDTO(s)..
.....
.....
Happy Learning 🙂