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.Video Tutorials
Project Structure
The following screenshot shows final structure of the project, Spring Batch scheduler Example.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
In the next screen, you enter the content as shown below then click Next
Name: SpringBatchQuartzExample
Group: com.jackrutorial
In the next step, you choose Spring Boot Version is 1.5.10 and choose the Batch dependencies I/O -> Batch, then click Finish
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