Overview

In this tutorial, we show you how to integrate Spring Batch Boot with Quartz scheduler to run batch job periodically. In this example, the Quartz scheduler with Spring job scheduler to run every 10 seconds.



Quartz-Scheduler-Annotation-&-Spring-Batch-in-Spring-Boot-Tutorial

Video Tutorials


Project Structure

The following screenshot shows final structure of the project, Spring Batch scheduler Example.

Project Structure Spring Boot Batch Quartz Scheduler

Creating the Project

Open Eclipse Oxygen java and install the Spring Tools Eclipse - Spring Tool Suite IDE.


Go to File -> New -> Other... Select Spring Starter Project under Spring Boot category then click Next as shown below


Spring Starter Project

In the next screen, you enter the content as shown below then click Next
Name: SpringBatchQuartzExample
Group: com.jackrutorial

new spring starter project

In the next step, you choose Spring Boot Version is 1.5.10 and choose the Batch dependencies I/O -> Batch, then click Finish


new spring starter project dependencies

Maven Dependencies

Add the following to your project's pom.xml

<dependency>
 <groupId>org.quartz-scheduler</groupId>
 <artifactId>quartz</artifactId>
 <version>2.2.3</version>
</dependency>

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context-support</artifactId>
 <version>4.3.0.RELEASE</version>
</dependency>

Following is the updated 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>com.jackrutorial</groupId>
 <artifactId>SpringBatchQuartzExample</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>SpringBatchQuartzExample</name>
 <description>Demo project for Spring Boot</description>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.10.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>org.quartz-scheduler</groupId>
   <artifactId>quartz</artifactId>
   <version>2.2.3</version>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>4.3.0.RELEASE</version>
  </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>

Creating a Custom ItemReader

Create a CustomItemReader class under com.jackrutorial.items package and write the following code in it.

package com.jackrutorial.items;

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 CustomItemReader implements ItemReader<Object> {

 @Override
 public Object read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
  return null;
 }
}

Creating a Custom ItemWriter

Create a CustomItemWriter class under com.jackrutorial.items package and write the following code in it.

package com.jackrutorial.items;

import java.util.List;

import org.springframework.batch.item.ItemWriter;

public class CustomItemWriter implements ItemWriter<Object> {

 @Override
 public void write(List<? extends Object> arg0) throws Exception {
  
 }
}

Configure Spring Batch Boot

Create a BatchConfiguration class under com.jackrutorial package and write the following code in it.

package com.jackrutorial;

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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.jackrutorial.items.CustomItemReader;
import com.jackrutorial.items.CustomItemWriter;

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
 
 @Autowired
 public JobBuilderFactory jobBuilderFactory;
 
 @Autowired
 public StepBuilderFactory stepBuilderFactory;
 
 @Bean
 public CustomItemReader reader() {
  return new CustomItemReader();
 }
 
 @Bean
 public CustomItemWriter writer() {
  return new CustomItemWriter();
 }
 
 @Bean
 public Step step1() {
  return stepBuilderFactory.get("step1")
    .<Object, Object> chunk(10)
    .reader(reader())
    .writer(writer())
    .build();
 }
 
 @Bean
 public Job testJob() {
  return jobBuilderFactory.get("testJob")
    .incrementer(new RunIdIncrementer())
    .flow(step1())
    .end()
    .build();
 }
}

Configure Spring Batch Quartz

Create a QuartzJobLauncher class under com.jackrutorial.quartz package and write the following code in it.

package com.jackrutorial.quartz;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class QuartzJobLauncher extends QuartzJobBean {

 private String jobName;
 private JobLauncher jobLauncher;
 private JobLocator jobLocator;

 public String getJobName() {
  return jobName;
 }

 public void setJobName(String jobName) {
  this.jobName = jobName;
 }

 public JobLauncher getJobLauncher() {
  return jobLauncher;
 }

 public void setJobLauncher(JobLauncher jobLauncher) {
  this.jobLauncher = jobLauncher;
 }

 public JobLocator getJobLocator() {
  return jobLocator;
 }

 public void setJobLocator(JobLocator jobLocator) {
  this.jobLocator = jobLocator;
 }

 @Override
 protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
  JobParameters jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis()).toJobParameters();
  
  try {
   Job job = jobLocator.getJob(jobName);
   JobExecution jobExecution = jobLauncher.run(job, jobParameters);
   
   System.out.println("########### Status: " + jobExecution.getStatus());
   
  } catch(JobExecutionAlreadyRunningException | JobRestartException | JobInstanceAlreadyCompleteException 
    | JobParametersInvalidException | NoSuchJobException  e) {
   e.printStackTrace();
  } 
 }

}

Create a QuartzConfig class under com.jackrutorial.quartz package and write the following code in it.

package com.jackrutorial.quartz;

import java.util.HashMap;
import java.util.Map;

import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
public class QuartzConfig {

 @Autowired
 private JobLauncher jobLauncher;
 
 @Autowired
 private JobLocator jobLocator;
 
 @Bean
 public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
  JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
  jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
  
  return jobRegistryBeanPostProcessor;
 }
 
 @Bean
 public JobDetailFactoryBean jobDetailFactoryBean() {
  JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
  jobDetailFactoryBean.setJobClass(QuartzJobLauncher.class);
  Map<String, Object> map = new HashMap<String, Object>();
  map.put("jobName", "testJob");
  map.put("jobLauncher", jobLauncher);
  map.put("jobLocator", jobLocator);
  
  jobDetailFactoryBean.setJobDataAsMap(map);
  
  return jobDetailFactoryBean;
 }
 
 @Bean
 public CronTriggerFactoryBean cronTriggerFactoryBean() {
  CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
  cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean().getObject());
  //run every 10 seconds
  cronTriggerFactoryBean.setCronExpression("*/10 * * * * ? *");
  
  return cronTriggerFactoryBean;
 }
 
 @Bean
 public SchedulerFactoryBean schedulerFactoryBean() {
  SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
  schedulerFactoryBean.setTriggers(cronTriggerFactoryBean().getObject());
  
  return schedulerFactoryBean;
 }
} 
 
We configure Quartz CRON trigger to specify when and with which periodicity the Spring job scheduler should run. Here, the Quartz scheduler to schedule Spring job scheduler to run every 10 seconds.

@Bean
public JobDetailFactoryBean jobDetailFactoryBean() {
 JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
 jobDetailFactoryBean.setJobClass(QuartzJobLauncher.class);
 Map<String, Object> map = new HashMap<String, Object>();
 map.put("jobName", "testJob");
 map.put("jobLauncher", jobLauncher);
 map.put("jobLocator", jobLocator);
 
 jobDetailFactoryBean.setJobDataAsMap(map);
 
 return jobDetailFactoryBean;
}

Update SpringBatchQuartzExampleApplication class under com.jackrutorial package and write the following code in it.

package com.jackrutorial;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;

@SpringBootApplication
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class SpringBatchQuartzExampleApplication {

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

Run Spring Boot Batch Scheduler

Right click to the SpringBatchQuartzExampleApplication class, select Run As -> Java Application. The Quartz scheduler will run the testJob every 10 seconds.

Below is the output:

2018-03-30 00:10:52.396  INFO 33624 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
2018-03-30 00:10:52.396  INFO 33624 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler version: 2.2.3
2018-03-30 00:10:52.396  INFO 33624 --- [           main] org.quartz.core.QuartzScheduler          : JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@403f0a22
2018-03-30 00:10:52.740  INFO 33624 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-03-30 00:10:52.740  INFO 33624 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 2147483647
2018-03-30 00:10:52.756  INFO 33624 --- [           main] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now
2018-03-30 00:10:52.756  INFO 33624 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.
2018-03-30 00:10:52.756  INFO 33624 --- [           main] o.s.b.a.b.JobLauncherCommandLineRunner   : Running default command line with: []
2018-03-30 00:10:52.818  INFO 33624 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=testJob]] launched with the following parameters: [{run.id=1}]
2018-03-30 00:10:52.849  INFO 33624 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
2018-03-30 00:10:52.912  INFO 33624 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=testJob]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED]
2018-03-30 00:10:52.912  INFO 33624 --- [           main] c.j.SpringBatchQuartzExampleApplication  : Started SpringBatchQuartzExampleApplication in 2.173 seconds (JVM running for 2.697)
2018-03-30 00:11:00.022  INFO 33624 --- [ryBean_Worker-1] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=testJob]] launched with the following parameters: [{time=1522343460007}]
2018-03-30 00:11:00.029  INFO 33624 --- [ryBean_Worker-1] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
2018-03-30 00:11:00.045  INFO 33624 --- [ryBean_Worker-1] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=testJob]] completed with the following parameters: [{time=1522343460007}] and the following status: [COMPLETED]
########### Status: COMPLETED
2018-03-30 00:11:10.009  INFO 33624 --- [ryBean_Worker-2] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=testJob]] launched with the following parameters: [{time=1522343470009}]
2018-03-30 00:11:10.035  INFO 33624 --- [ryBean_Worker-2] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
2018-03-30 00:11:10.051  INFO 33624 --- [ryBean_Worker-2] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=testJob]] completed with the following parameters: [{time=1522343470009}] and the following status: [COMPLETED]
########### Status: COMPLETED
2018-03-30 00:11:20.005  INFO 33624 --- [ryBean_Worker-3] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=testJob]] launched with the following parameters: [{time=1522343480005}]
2018-03-30 00:11:20.017  INFO 33624 --- [ryBean_Worker-3] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
2018-03-30 00:11:20.032  INFO 33624 --- [ryBean_Worker-3] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=testJob]] completed with the following parameters: [{time=1522343480005}] and the following status: [COMPLETED]
########### Status: COMPLETED

References

Previous Post
Next Post

post written by: