Spring Batch + Quartz Scheduler example
In this tutorial, we will show you how to use the Quartz scheduler framework to schedule a Spring batch job to run every 10 seconds.
Tools and libraries used
- Maven 3
- Eclipse 4.2
- JDK 1.6
- Spring Core 3.2.2.RELEASE
- Spring Batch 2.2.0.RELEASE
- Quartz 1.8.6
The relationship looks like the following :
Spring Batch <--> Spring QuartzJobBean <--> Quartz Frameworks
The QuartzJobBean
is acts like a bridge between Spring batch and Quartz frameworks.
1. Project Dependencies
Spring need spring-context-support
to support Quartz scheduler.
<project ...
<properties>
<jdk.version>1.6</jdk.version>
<spring.version>3.2.2.RELEASE</spring.version>
<spring.batch.version>2.2.0.RELEASE</spring.batch.version>
<quartz.version>1.8.6</quartz.version>
</properties>
<dependencies>
<!-- Spring Core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- QuartzJobBean in spring-context-support.jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Quartz framework -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
</dependency>
<!-- Spring Batch dependencies -->
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</artifactId>
<version>${spring.batch.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-infrastructure</artifactId>
<version>${spring.batch.version}</version>
</dependency>
</dependencies>
</project>
2. Spring Batch Jobs
A batch job to read a csv file and print out the content via a custom writer. Few points to highlight :
1. Configure the JobRegistryBeanPostProcessor
bean, it registers Job
beans with JobRegistry
, so that QuartzJobBean
is able to get the Job
bean via JobRegister
(JobLocator).
2. The JobLauncherDetails
is extended QuartzJobBean
, acts as a bridge between Spring batch and Quartz.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-2.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
">
<!-- spring batch context -->
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="transactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean
class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry" />
</bean>
<bean id="jobRegistry"
class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
<!-- spring batch context -->
<bean id="report" class="com.mkyong.model.Report" scope="prototype" />
<bean id="customWriter" class="com.mkyong.writers.CustomWriter" />
<batch:job id="reportJob">
<batch:step id="step1">
<batch:tasklet>
<batch:chunk reader="cvsFileItemReader" writer="customWriter"
commit-interval="10">
</batch:chunk>
</batch:tasklet>
</batch:step>
</batch:job>
<bean id="cvsFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
<!-- Read a csv file -->
<property name="resource" value="classpath:cvs/input/report.csv" />
<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="id,impressions" />
</bean>
</property>
<property name="fieldSetMapper">
<bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="report" />
</bean>
</property>
</bean>
</property>
</bean>
<!-- run every 10 seconds -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="*/10 * * * * ?" />
</bean>
</property>
</bean>
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.mkyong.quartz.JobLauncherDetails" />
<property name="group" value="quartz-batch" />
<property name="jobDataAsMap">
<map>
<entry key="jobName" value="reportJob" />
<entry key="jobLocator" value-ref="jobRegistry" />
<entry key="jobLauncher" value-ref="jobLauncher" />
<entry key="param1" value="mkyong1" />
<entry key="param2" value="mkyong2" />
</map>
</property>
</bean>
</beans>
3. QuartzJobBean Example
This class is copied from Spring batch sample Github repository, with minor change to run the completed job by passing a new Date()
each time the job is running.
package com.mkyong.quartz;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import org.quartz.JobExecutionContext;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class JobLauncherDetails extends QuartzJobBean {
static final String JOB_NAME = "jobName";
private JobLocator jobLocator;
private JobLauncher jobLauncher;
public void setJobLocator(JobLocator jobLocator) {
this.jobLocator = jobLocator;
}
public void setJobLauncher(JobLauncher jobLauncher) {
this.jobLauncher = jobLauncher;
}
@SuppressWarnings("unchecked")
protected void executeInternal(JobExecutionContext context) {
Map<String, Object> jobDataMap = context.getMergedJobDataMap();
String jobName = (String) jobDataMap.get(JOB_NAME);
JobParameters jobParameters = getJobParametersFromJobMap(jobDataMap);
try {
jobLauncher.run(jobLocator.getJob(jobName), jobParameters);
} catch (JobExecutionException e) {
e.printStackTrace();
}
}
//get params from jobDataAsMap property, job-quartz.xml
private JobParameters getJobParametersFromJobMap(Map<String, Object> jobDataMap) {
JobParametersBuilder builder = new JobParametersBuilder();
for (Entry<String, Object> entry : jobDataMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof String && !key.equals(JOB_NAME)) {
builder.addString(key, (String) value);
} else if (value instanceof Float || value instanceof Double) {
builder.addDouble(key, ((Number) value).doubleValue());
} else if (value instanceof Integer || value instanceof Long) {
builder.addLong(key, ((Number) value).longValue());
} else if (value instanceof Date) {
builder.addDate(key, (Date) value);
} else {
// JobDataMap contains values which are not job parameters
// (ignoring)
}
}
//need unique job parameter to rerun the completed job
builder.addDate("run date", new Date());
return builder.toJobParameters();
}
}
4. Run It
Loads the Spring application context, the Quartz scheduler will run the “reportJob” every 10 seconds.
package com.mkyong;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
String springConfig = "spring/batch/jobs/job-quartz.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(springConfig);
}
}
Thanks for this post. I have a question about the cron expression.
Can i set different cron expressions for different environments?
SIT – Every day
UAT-End of month
Pre Prod-End of month
Prod-End of month
This will help in testing the job and its scheduler in SIT
Very Good ! How we can write junit test cases for this project ?
Thanks!!!! it’s possible to schedule the job dynamically? By reading the value in table (for example)
Thanks for this tutorial… It is really useful. I have a small question in this tutorial you are having a map repository for Spring Batch Job and JobRegistry works on reading all Map data. What if I am forced to have a Database Job Repository for Spring Batch. I was not able to find any class for JobRegistry which supports DB based repository. If you can give any leads on that front will be great. Thanks in advance
Thanks for your kindness tutorials. They are very useful.
I have a question.. What if I’d like to run 2 jobs in this sample Spring Batch project?
Do I have to make another xml files such as “job-quartz2.xml”, to make another jobRepository??
I thought I have to add one more tag(for another job) in “jobDetail” bean, but this caused an error like,
“Invalid content was found starting with element ‘map’.”…
Spring Batch Quartz Scheduler Example is giving error “java.lang.NoClassDefFoundError: org.slf4j.impl.StaticLoggerBinder
”
java.lang.NoClassDefFoundError: org.slf4j.impl.StaticLoggerBinder
“
Hello,
I follow most of your tutorials, as they are always useful and seem to be the best source for an answer. However, I could use some additional help. How would I take a simple java application like this and convert it into an Enterprise App (ear)?