Spring Batch I
Spring Batch I
Step 1 - Create Spring Starter Project and create packages and classes like below
image
<groupId>com.spring.batch</groupId>
<artifactId>SpringBatchManually</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBatchManually</name>
<description>Spring Batch Manually Using Spring
Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.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>1.8</java.version>
</properties>
<dependencies>
<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>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>
Step 3 - Once all the dependencies downloaded successfully have to enable batch
processing in main class
package com.spring.batch;
import
org.springframework.batch.core.configuration.annotation.EnableBa
tchProcessing;
import org.springframework.boot.SpringApplication;
import
org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableBatchProcessing
public class Application {
package com.spring.batch.config;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import
org.springframework.batch.core.configuration.annotation.JobBuild
erFactory;
import
org.springframework.batch.core.configuration.annotation.StepBuil
derFactory;
import
org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.beans.factory.config.AutowireCapableBeanFact
ory;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import com.spring.batch.tasklet.ExecuteTasklet;
@Component
public class BatchConfiguration {
private @Autowired AutowireCapableBeanFactory beanFactory;
@Bean(name="executeManualJob")
public Job executeCommandJob(JobBuilderFactory jobs, Step
step) {
return jobs.get("executeManualJob")
.incrementer(new RunIdIncrementer())
.flow(step)
.end()
.build();
}
@Bean
public Step step(StepBuilderFactory stepBuilderFactory) {
return stepBuilderFactory.get("step")
.tasklet(executeJobTasklet())
.build();
}
package com.spring.batch.tasklet;
import org.springframework.batch.core.StepContribution;
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.stereotype.Service;
import com.spring.batch.service.BatchService;
@Service
public class ExecuteTasklet implements Tasklet {
@Autowired
private BatchService batchService;
@Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext chunkContext) throws Exception {
String type =
chunkContext.getStepContext().getJobParameters().get("type").toS
tring();
batchService.executeJobManually(type);
return RepeatStatus.FINISHED;
}
}
Step 6 - Create a controller
package com.spring.batch.controller;
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.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BatchController {
@Autowired
private JobLauncher jobLauncher;
@Autowired
@Qualifier("executeManualJob")
private Job job;
@RequestMapping("/{type}")
public void executeManualJob(@PathVariable String type) {
try {
JobParameters jobParameters = new JobParametersBuilder()
.addString("type", type)
.addLong("time", System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(job,
jobParameters).getExitStatus().getExitCode();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.spring.batch.service.impl;
import org.springframework.stereotype.Service;
import com.spring.batch.service.BatchService;
@Service
public class BatchServiceImpl implements BatchService {
@Override
public void executeJobManually(String type) {
System.out.println("Job execution start");
}
}
Step 9 - added a configuration for database
server.context-path=/springbatch
spring.datasource.url=jdbc:mysql://localhost:3306/spring-batch
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.batch.job.enabled: false
Here is the output -
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
[32m :: Spring Boot :: [39m [2m (v1.4.2.RELEASE) [0;39m
[2m2016-11-14 18:34:11.568 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mcom.spring.batch.Application [0;39m [2m: [0;39m
Starting Application on LTP-O-000441 with PID 5416
(D:\MLMigrationToolkit\MigrationApp\SpringBatchManually\target\c
lasses started by JEETPS in
D:\MLMigrationToolkit\MigrationApp\SpringBatchManually)
[2m2016-11-14 18:34:11.573 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mcom.spring.batch.Application [0;39m [2m: [0;39m
No active profile set, falling back to default profiles: default
[2m2016-11-14 18:34:11.673 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mationConfigEmbeddedWebApplicationContext [0;39m [2m: [0;39m
Refreshing
org.springframework.boot.context.embedded.AnnotationConfigEmbedd
edWebApplicationContext@4550bb58: startup date [Mon Nov 14
18:34:11 IST 2016]; root of context hierarchy
[2m2016-11-14 18:34:14.169 [0;39m [33m WARN [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.c.a.ConfigurationClassEnhancer [0;39m [2m: [0;39m
@Bean method ScopeConfiguration.stepScope is non-static and
returns an object assignable to Spring's
BeanFactoryPostProcessor interface. This will result in a
failure to process annotations such as @Autowired, @Resource and
@PostConstruct within the method's declaring @Configuration
class. Add the 'static' modifier to this method to avoid these
container lifecycle issues; see @Bean javadoc for complete
details.
[2m2016-11-14 18:34:14.188 [0;39m [33m WARN [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.c.a.ConfigurationClassEnhancer [0;39m [2m: [0;39m
@Bean method ScopeConfiguration.jobScope is non-static and
returns an object assignable to Spring's
BeanFactoryPostProcessor interface. This will result in a
failure to process annotations such as @Autowired, @Resource and
@PostConstruct within the method's declaring @Configuration
class. Add the 'static' modifier to this method to avoid these
container lifecycle issues; see @Bean javadoc for complete
details.
[2m2016-11-14 18:34:14.420 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mtrationDelegate$BeanPostProcessorChecker [0;39m [2m: [0;39m
Bean
'org.springframework.transaction.annotation.ProxyTransactionMana
gementConfiguration' of type [class
org.springframework.transaction.annotation.ProxyTransactionManag
ementConfiguration$$EnhancerBySpringCGLIB$$bd712ed9] is not
eligible for getting processed by all BeanPostProcessors (for
example: not eligible for auto-proxying)
[2m2016-11-14 18:34:15.180 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36ms.b.c.e.t.TomcatEmbeddedServletContainer [0;39m [2m: [0;39m
Tomcat initialized with port(s): 8080 (http)
[2m2016-11-14 18:34:15.202 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.apache.catalina.core.StandardService [0;39m [2m: [0;39m
Starting service Tomcat
[2m2016-11-14 18:34:15.203 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36morg.apache.catalina.core.StandardEngine [0;39m [2m: [0;39m
Starting Servlet Engine: Apache Tomcat/8.5.6
[2m2016-11-14 18:34:15.408 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ost-startStop-1] [0;39m
[36mo.a.c.c.C.[.[localhost].[/springbatch] [0;39m [2m: [0;39m
Initializing Spring embedded WebApplicationContext
[2m2016-11-14 18:34:15.409 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ost-startStop-1] [0;39m
[36mo.s.web.context.ContextLoader [0;39m [2m: [0;39m
Root WebApplicationContext: initialization completed in 3742 ms
[2m2016-11-14 18:34:15.739 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ost-startStop-1] [0;39m
[36mo.s.b.w.servlet.ServletRegistrationBean [0;39m [2m: [0;39m
Mapping servlet: 'dispatcherServlet' to [/]
[2m2016-11-14 18:34:15.744 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ost-startStop-1] [0;39m
[36mo.s.b.w.servlet.FilterRegistrationBean [0;39m [2m: [0;39m
Mapping filter: 'characterEncodingFilter' to: [/*]
[2m2016-11-14 18:34:15.745 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ost-startStop-1] [0;39m
[36mo.s.b.w.servlet.FilterRegistrationBean [0;39m [2m: [0;39m
Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
[2m2016-11-14 18:34:15.745 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ost-startStop-1] [0;39m
[36mo.s.b.w.servlet.FilterRegistrationBean [0;39m [2m: [0;39m
Mapping filter: 'httpPutFormContentFilter' to: [/*]
[2m2016-11-14 18:34:15.745 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ost-startStop-1] [0;39m
[36mo.s.b.w.servlet.FilterRegistrationBean [0;39m [2m: [0;39m
Mapping filter: 'requestContextFilter' to: [/*]
[2m2016-11-14 18:34:16.598 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mj.LocalContainerEntityManagerFactoryBean [0;39m [2m: [0;39m
Building JPA container EntityManagerFactory for persistence unit
'default'
[2m2016-11-14 18:34:16.623 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.hibernate.jpa.internal.util.LogHelper [0;39m [2m: [0;39m
HHH000204: Processing PersistenceUnitInfo [
name: default
...]
[2m2016-11-14 18:34:16.752 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36morg.hibernate.Version [0;39m [2m: [0;39m
HHH000412: Hibernate Core {5.0.11.Final}
[2m2016-11-14 18:34:16.756 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36morg.hibernate.cfg.Environment [0;39m [2m: [0;39m
HHH000206: hibernate.properties not found
[2m2016-11-14 18:34:16.759 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36morg.hibernate.cfg.Environment [0;39m [2m: [0;39m
HHH000021: Bytecode provider name : javassist
[2m2016-11-14 18:34:16.860 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.hibernate.annotations.common.Version [0;39m [2m: [0;39m
HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
[2m2016-11-14 18:34:17.172 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36morg.hibernate.dialect.Dialect [0;39m [2m: [0;39m
HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
[2m2016-11-14 18:34:17.480 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mj.LocalContainerEntityManagerFactoryBean [0;39m [2m: [0;39m
Initialized JPA EntityManagerFactory for persistence unit
'default'
[2m2016-11-14 18:34:18.109 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36ms.w.s.m.m.a.RequestMappingHandlerAdapter [0;39m [2m: [0;39m
Looking for @ControllerAdvice:
org.springframework.boot.context.embedded.AnnotationConfigEmbedd
edWebApplicationContext@4550bb58: startup date [Mon Nov 14
18:34:11 IST 2016]; root of context hierarchy
[2m2016-11-14 18:34:18.253 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36ms.w.s.m.m.a.RequestMappingHandlerMapping [0;39m [2m: [0;39m
Mapped "{[/{type}]}" onto public void
com.spring.batch.controller.BatchController.executeManualJob(jav
a.lang.String)
[2m2016-11-14 18:34:18.259 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36ms.w.s.m.m.a.RequestMappingHandlerMapping [0;39m [2m: [0;39m
Mapped "{[/error]}" onto public
org.springframework.http.ResponseEntity>
org.springframework.boot.autoconfigure.web.BasicErrorController.
error(javax.servlet.http.HttpServletRequest)
[2m2016-11-14 18:34:18.259 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36ms.w.s.m.m.a.RequestMappingHandlerMapping [0;39m [2m: [0;39m
Mapped "{[/error],produces=[text/html]}" onto public
org.springframework.web.servlet.ModelAndView
org.springframework.boot.autoconfigure.web.BasicErrorController.
errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.ht
tp.HttpServletResponse)
[2m2016-11-14 18:34:18.310 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.w.s.handler.SimpleUrlHandlerMapping [0;39m [2m: [0;39m
Mapped URL path [/webjars/**] onto handler of type [class
org.springframework.web.servlet.resource.ResourceHttpRequestHand
ler]
[2m2016-11-14 18:34:18.310 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.w.s.handler.SimpleUrlHandlerMapping [0;39m [2m: [0;39m
Mapped URL path [/**] onto handler of type [class
org.springframework.web.servlet.resource.ResourceHttpRequestHand
ler]
[2m2016-11-14 18:34:18.399 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.w.s.handler.SimpleUrlHandlerMapping [0;39m [2m: [0;39m
Mapped URL path [/**/favicon.ico] onto handler of type [class
org.springframework.web.servlet.resource.ResourceHttpRequestHand
ler]
[2m2016-11-14 18:34:18.609 [0;39m [33m WARN [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.b.a.batch.BasicBatchConfigurer [0;39m [2m: [0;39m
JPA does not support custom isolation levels, so locks may not
be taken when launching Jobs
[2m2016-11-14 18:34:18.618 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.b.c.r.s.JobRepositoryFactoryBean [0;39m [2m: [0;39m
No database type set, using meta data indicating: MYSQL
[2m2016-11-14 18:34:18.816 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.b.c.l.support.SimpleJobLauncher [0;39m [2m: [0;39m
No TaskExecutor has been set, defaulting to synchronous
executor.
[2m2016-11-14 18:34:18.852 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.jdbc.datasource.init.ScriptUtils [0;39m [2m: [0;39m
Executing SQL script from class path resource
[org/springframework/batch/core/schema-mysql.sql]
[2m2016-11-14 18:34:18.923 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.jdbc.datasource.init.ScriptUtils [0;39m [2m: [0;39m
Executed SQL script from class path resource
[org/springframework/batch/core/schema-mysql.sql] in 71 ms.
[2m2016-11-14 18:34:19.266 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mo.s.j.e.a.AnnotationMBeanExporter [0;39m [2m: [0;39m
Registering beans for JMX exposure on startup
[2m2016-11-14 18:34:19.507 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36ms.b.c.e.t.TomcatEmbeddedServletContainer [0;39m [2m: [0;39m
Tomcat started on port(s): 8080 (http)
[2m2016-11-14 18:34:19.515 [0;39m [32m INFO [0;39m [35m5416
[0;39m [2m--- [0;39m [2m[ main] [0;39m
[36mcom.spring.batch.Application [0;39m [2m: [0;39m
Started Application in 8.707 seconds (JVM running for 9.605)
Once run successfully create tables in db like below image
Enlace
https://java-is-everywhere.blogspot.com/2016/11/how-to-execute-spring-batch-job.html
Table of Contents
Part 1: Introduction and Functional Specs
Part 2: Java classes
Part 3: XML configuration
Part 4: Running the Application
Dependencies
Spring core 3.1.0.RELEASE
Spring Batch 2.1.8.RELEASE
See pom.xml for details
Github
To access the source code, please visit the project's Github repository (click here)
Functional Specs
Before we start, let's define the application's specs as follows:
User Files
user1.csv
This file contains comma-separated value (CSV) records representing User records. Each line has
the following tokens: username, first name, last name, password.
user2.csv
This file contains fixed-length records representing User records. Each line has the following
tokens: username(positions 1-5), first name(6-9), last name(10-16), password(17-25).
ryangRyanGirardicvbvhtrn3
marypMaryPoppinsxcty68xgf
diancDianCruisedhgfhrt556
user2.csv
This file contains comma-separated value and fixed-length records representing User records. Each
line has the following tokens: username, first name, last name, password.
DELIMITED-RECORD-A;kyle,Kyle,Smith,43sdtsdf4
FIXED-RECORD-B;elaineElaineRogers.4z456gff
DELIMITED-RECORD-B;mark|Mark|Johnson|1683fjjs
Role Files
role1.csv
This file contains comma-separated value (CSV) records representing Role records. Each line has
the following tokens: username and access level.
john 1
jane 2
mike 1
role2.csv
This file contains fixed-length records representing Role records. Each line has the following
tokens: username and access level.
ryang1
maryp2
dianc1
role3.csv
This file contains comma-separated value (CSV) records representing Role records. Each line has
the following tokens: username and access level.
ralph 1
kyle 2
elaine 1
mark 2
By now you should have a basic idea of the file formats that we will be importing. You must realize
that all we want to do is import these files and display them on a web interface.
Diagrams
Here's the Class diagram:
Screenshots
Let's preview how the application will look like after it's finished. This is also a good way to clarify
further the application's specs.
Entry page
The entry page is the primary page that users will see. It contains a table showing user records and
four buttons for adding, editing, deleting, and reloading data. All interactions will happen in this
page.
Entry page
Next
In the next section, we will write the Java classes. Click here to proceed.
Table of Contents
Part 1: Introduction and Functional Specs
Part 2: Java classes
Part 3: XML configuration
Part 4: Running the Application
Project Structure
Our application is a Maven project and therefore follows Maven structure. As we create the classes,
we've organized them in logical layers: domain, repository, service, and controller.
The Layers
Disclaimer
I will only discuss the Spring Batch-related classes here. And I've purposely left out the unrelated
classes because I have described them in detail already from my previous tutorials. See the
following guides:
Spring MVC 3.1 - Implement CRUD with Spring Data Redis (Part 3)
Spring MVC 3.1 - Implement CRUD with Spring Data MongoDB (Part 3)
Spring MVC 3.1, jqGrid, and Spring Data JPA Integration Guide (Part 2)
Controller Layer
The BatchJobController handles batch requests. There are three job mappings:
/job1
/job2
/job3
Everytime a job is run, a new JobParameter is initialized as the job's parameter. We use the current
date to be the distinguishing parameter. This means every job trigger is considered a new job.
What is a JobParameter?
"how is one JobInstance distinguished from another?" The answer is: JobParameters.
JobParameters is a set of parameters used to start a batch job. They can be used for identification
or even as reference data during the run:
Notice we have injected a JobLauncher. It's primary job is to start our jobs. Each job will run
asynchronously (this is declared in the XML configuration).
What is a JobLauncher?
JobLauncher represents a simple interface for launching a Job with a given set of JobParameters:
package org.krams.controller;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.krams.dto.StatusResponse;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/batch")
@Autowired
@Autowired @Qualifier("batchJob1")
@Autowired @Qualifier("batchJob2")
@Autowired @Qualifier("batchJob3")
@RequestMapping(value="/job1")
try {
Map<String,JobParameter> parameters = new
HashMap<String,JobParameter>();
} catch (Exception e) {
@RequestMapping(value="/job2")
try {
} catch (Exception e) {
}
}
@RequestMapping(value="/job3")
try {
} catch (Exception e) {
Batch Layer
This layer contains various helper classes to aid us in processing batch files.
package org.krams.batch;
import org.krams.domain.User;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
@Override
if(fs == null){
return null;
user.setUsername(fs.readString("username"));
user.setPassword(fs.readString("password"));
user.setLastName(fs.readString("lastName"));
user.setFirstName(fs.readString("firstName"));
return user;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.krams.domain.Role;
import org.krams.domain.User;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@Autowired
"firstName, " +
"lastName, " +
"password, " +
"username " +
if(fs == null){
return null;
params[0] = fs.readString("username");
@Override
user.setId(rs.getLong("id"));
user.setFirstName(rs.getString("firstName"));
user.setLastName(rs.getString("lastName"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
}));
role.setRole(fs.readInt("role"));
return role;
package org.krams.batch;
import org.krams.domain.User;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
@Override
if(fs == null){
return null;
user.setPassword(fs.readString("password"));
user.setLastName(fs.readString("lastName"));
user.setFirstName(fs.readString("firstName"));
return user;
if (tokens.length == 2) {
return tokens[1];
return token;
package org.krams.batch;
import java.util.List;
import org.krams.domain.User;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
public class UserItemWriter implements ItemWriter<User> {
@Autowired
"lastName, " +
"password, " +
"username) " +
"values(?,?,?,?);";
@Override
jdbcTemplate.update(INSERT_QUERY, user.getFirstName(),
user.getLastName(), user.getPassword(), user.getUsername());
package org.krams.batch;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.krams.domain.Role;
import org.krams.domain.User;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@Autowired
"firstName, " +
"lastName, " +
"password, " +
"username " +
"values(?,?);";
@Override
params[0] = role.getUser().getUsername();
@Override
user.setId(rs.getLong("id"));
user.setFirstName(rs.getString("firstName"));
user.setLastName(rs.getString("lastName"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
});
Table of Contents
Part 1: Introduction and Functional Specs
Part 2: Java classes
Part 3: XML configuration
Part 4: Running the Application
Configuration
Properties File
The spring.properties file contains the database name and CSV files that we will import.
A job.commit.interval property is also specified which denotes how many records to commit per
interval.
# database properties
app.jdbc.driverClassName=com.mysql.jdbc.Driver
app.jdbc.url=jdbc\:mysql\://localhost/spring_batch_tutorial
app.jdbc.username=root
app.jdbc.password=
# batch properties
user1.file.name=user1.csv
role1.file.name=role1.csv
user2.file.name=user2.csv
role2.file.name=role2.csv
user3.file.name=user3.csv
role3.file.name=role3.csv
job.commit.interval=2
Spring Batch
To configure a Spring Batch job, we have to declare the infrastructure-related beans first. Here are
the beans that needs to be declared:
Spring Batch is a lightweight, comprehensive batch framework designed to enable the development
of robust batch applications vital for the daily operations of enterprise systems. Spring Batch builds
upon the productivity, POJO-based development approach, and general ease of use capabilities
people have come to know from the Spring Framework, while making it easy for developers to
access and leverage more advance enterprise services when necessary. Spring Batch is not a
scheduling framework.
What is a JobRepository?
JobRepository is the persistence mechanism for all of the Stereotypes mentioned above. It provides
CRUD operations for JobLauncher, Job, and Step implementations.
JobLauncher represents a simple interface for launching a Job with a given set of JobParameters
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-
3.1.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-
2.1.xsd">
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher"
p:jobRepository-ref="jobRepository"
p:taskExecutor-ref="taskExecutor"/>
<bean id="taskExecutor"
class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
<!-- http://forum.springsource.org/showthread.php?59779-Spring-Batch-1-1-2-
Standard-JPA-does-not-support-custom-isolation-levels-use-a-sp -->
<job-repository id="jobRepository"
xmlns="http://www.springframework.org/schema/batch"
data-source="jpaDataSource"
isolation-level-for-create="DEFAULT"
transaction-manager="transactionManager"/>
p:dataSource-ref="jpaDataSource" />
</beans>
Job Anatomy
Before we start writing our jobs, let's examine first what constitutes a job.
What is a Job?
A Job is an entity that encapsulates an entire batch process. As is common with other Spring
projects, a Job will be wired together via an XML configuration file
Each job contains a series of steps. For each of step, a reference to an ItemReader and
an ItemWriter is also included. The reader's purpose is to read records for further processing, while
the writer's purpose is to write the records (possibly in a different format).
What is a Step?
A Step is a domain object that encapsulates an independent, sequential phase of a batch job.
Therefore, every Job is composed entirely of one or more steps. A Step contains all of the
information necessary to define and control the actual batch processing.
What is an ItemReader?
Although a simple concept, an ItemReader is the means for providing data from many different
types of input. The most general examples include: Flat File, XML, Database
What is an ItemWriter?
ItemWriter is similar in functionality to an ItemReader, but with inverse operations. Resources still
need to be located, opened and closed but they differ in that an ItemWriter writes out, rather than
reading in.
The Jobs
As discussed in part 1, we have three jobs.
What is DelimitedLineTokenizer?
Used for files where fields in a record are separated by a delimiter. The most common delimiter is a
comma, but pipes or semicolons are often used as well.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-
3.1.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-
2.1.xsd">
<tasklet>
commit-interval="${job.commit.interval}" />
</tasklet>
</step>
<step id="roleLoad1">
<tasklet>
commit-interval="${job.commit.interval}" />
</tasklet>
</step>
</job>
<bean id="userFileItemReader1"
class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="lineMapper">
<bean
class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names"
value="username,firstName,lastName,password" />
</bean>
</property>
<property name="fieldSetMapper">
<bean class="org.krams.batch.UserFieldSetMapper" />
</property>
</bean>
</property>
</bean>
<bean id="roleFileItemReader1"
class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="lineMapper">
<bean
class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
</bean>
</property>
<property name="fieldSetMapper">
</property>
</bean>
</property>
</bean>
</beans>
Notice userLoad2 is using FixedLengthTokenizer and the properties to be matched are the
following: username, firstName, lastName, password. However, instead of matching them based on
a delimiter, each token is matched based on a specified length: 1-5, 6-9, 10-16, 17-25 where 1-
5 represents the username and so forth. The same idea applies to roleLoad2.
What is FixedLengthTokenizer?
Used for files where fields in a record are each a 'fixed width'. The width of each field must be
defined for each record type.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-
3.1.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-
2.1.xsd">
<tasklet>
commit-interval="${job.commit.interval}" />
</tasklet>
</step>
<step id="roleLoad2">
<tasklet>
commit-interval="${job.commit.interval}" />
</tasklet>
</step>
</job>
<bean id="userFileItemReader2"
class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="lineMapper">
<bean
class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.FixedLengthTokenizer">
<property name="names"
value="username,firstName,lastName,password" />
</bean>
</property>
<property name="fieldSetMapper">
</property>
</bean>
</property>
</bean>
<bean id="roleFileItemReader2"
class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="resource" value="classpath:${role2.file.name}" />
<property name="lineMapper">
<bean
class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.FixedLengthTokenizer">
</bean>
</property>
<property name="fieldSetMapper">
</property>
</bean>
</property>
</bean>
</beans>
Job 3 is a mixed of Job 1 and Job 2. In order to mix both, we have to set
our lineMapper to PatternMatchingCompositeLineMapper.
What is PatternMatchingCompositeLineMapper?
Determines which among a list of LineTokenizers should be used on a particular line by checking
against a pattern.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-
3.1.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-
2.1.xsd">
<tasklet>
commit-interval="${job.commit.interval}" />
</tasklet>
</step>
<step id="roleLoad3">
<tasklet>
commit-interval="${job.commit.interval}" />
</tasklet>
</step>
</job>
<bean id="userFileItemReader3"
class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="lineMapper">
<bean
class="org.springframework.batch.item.file.mapping.PatternMatchingCompositeLineMapper">
<property name="tokenizers">
<map>
</map>
</property>
<property name="fieldSetMappers">
<map>
</map>
</property>
</bean>
</property>
</bean>
<bean id="multiUserFieldSetMapper"
class="org.krams.batch.MultiUserFieldSetMapper" />
<bean id="fixedLengthLineATokenizer"
class="org.springframework.batch.item.file.transform.FixedLengthTokenizer"
p:names="username,firstName,lastName,password"
p:names="username,firstName,lastName,password"
<bean id="delimitedATokenizer"
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
p:names="username,firstName,lastName,password"/>
<bean id="delimitedBTokenizer"
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
p:names="username,firstName,lastName,password"
p:delimiter="|"/>
<bean id="roleFileItemReader3"
class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="lineMapper">
<bean
class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
</bean>
</property>
<property name="fieldSetMapper">
</bean>
</property>
</bean>
</beans>
Table of Contents
Part 1: Introduction and Functional Specs
Part 2: Java classes
Part 3: XML configuration
Part 4: Running the Application
Conclusion
That's it! We've have successfully completed our Spring Batch application and learned how to
process of jobs in batches. We've also added Spring MVC support to allow jobs to be controlled
online.
I hope you've enjoyed this tutorial. Don't forget to check my other tutorials at the Tutorials section.