spring

Spring Batch Quartz Example

In this article we present an example of scheduling a Spring Batch job to run with a Quartz scheduler. This will be a simple job that executes a Tasklet. We will use an HSQL(which is an in-memory) database table.

The Tasklet will be scheduled to read some data from the table periodically. The sections have been organized as shown below. The example code is available for download at the end of the article.
 
 
 
 
 
 

 

1. Introduction

Before we delve into the example code, here’s a quick reference of the core concepts involved in this example. More content on Spring Batch have been detailed in another JCG article here. Those already familiar with these concepts may skip to the example directly.

  • Job: A Job encapsulates the entire batch process. It consists of one or more Steps. A Job is a simple interface and SimpleJob is its simplest implementation provided by the Spring Batch framework.
  • Step: A Step is a sequential phase of a batch job. All the processing in a Job actually happens in a Step.
  • Tasklet: It is one of the ways in which Step processing could be done. It is used when a Step involves just one task like running a Stored Procedure, making a remote call or executing some method etc.
  • Quartz Scheduler: It is an open-source scheduling library. It is simple and easy-to-use to schedule the running of a job periodically.

2. Example Code

In this example, we will set-up a Tasklet that reads data from an HSQL database table and prints it to the console. The Tasklet will be scheduled to run periodically using a Quartz Scheduler.

2.1 Tools Used

Following are the tools used:

  • Eclipse Helios
  • Spring Batch 3.0.4.RELEASE
  • Spring 4.0.5.RELEASE
  • Quartz 1.8.5
  • HSQL 1.8.0.7

2.2 Project Set-Up

  • Fire up Eclipse from a suitable location
  • Go to File->New->Project… as shown in the screenshot below

    Fig.1 Create Project
    Fig.1 Create Project
  • We will create a Maven project. So click on Project and in the wizard window choose the creation of a Maven project

    Fig.2 Choose the Creation of a Maven Project
    Fig.2 Choose the Creation of a Maven Project
  • This will be a simple Maven project and we will skip the ‘archeType’ selection as shown below

    Fig.3 Skip ArcheType Selection
    Fig.3 Skip ArcheType Selection
  • In the next window that comes up, provide the project details. We will use the following.

    Fig.4 Supply Project Name
    Fig.4 Supply Project Name
  • Next, we will add some folders to the project so that we have the final project structure as shown in the following screenshot.

    Fig. 5 Final Project Structure
    Fig. 5 Final Project Structure

2.3 Maven Dependency

Open the pom.xml file and add the following dependencies in it.
pom.xml

<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.javacodegeeks.exampl</groupId>
  <artifactId>SpringBatchQuartz</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <properties>
    <spring.version>4.0.5.RELEASE</spring.version>
  	<spring.batch.version>3.0.4.RELEASE</spring.batch.version>
  	<quartz.scheduler.version>1.8.5</quartz.scheduler.version>
  	<spring.jdbc.version>4.0.5.RELEASE</spring.jdbc.version>
  	<hsql.version>1.8.0.7</hsql.version>
  	<commons.version>1.4</commons.version>
  </properties>
  <dependencies>
    <dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-support</artifactId>
		<version>${spring.version}</version>
    </dependency>
  	<dependency>
		<groupId>org.springframework.batch</groupId>
		<artifactId>spring-batch-core</artifactId>
		<version>${spring.batch.version}</version>
    </dependency>   
    <dependency>
		<groupId>org.quartz-scheduler</groupId>
		<artifactId>quartz</artifactId>
		<version>${quartz.scheduler.version}</version>
	</dependency>  
	<dependency>
		<groupId>hsqldb</groupId>
		<artifactId>hsqldb</artifactId>
		<version>${hsql.version}</version>
	</dependency>   
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>${spring.jdbc.version}</version>		
   </dependency>
     <dependency>
		<groupId>commons-dbcp</groupId>
		<artifactId>commons-dbcp</artifactId>
		<version>${commons.version}</version>
	</dependency>	
  </dependencies>
</project>

Note: Spring Quartz 2.x is not compatible with Spring Batch 3.x. Hence, we are using Quartz version 1.x. Using uncompatible versions could trigger exceptions of the following sort.

Caused by: java.lang.IncompatibleClassChangeError: 
	class org.springframework.scheduling.quartz.JobDetailBean has interface org.quartz.JobDetail as super class
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:615)

2.4 Create HSQL Table

Since our Tasklet will be reading from an HSQL database table, we will write a short script to create a table and insert just one record into it. It will be placed under the ‘hsqldb’ folder as shown in the project structure snapshot above (refer Fig.5). Note that one might achieve much more meaningful tasks in a Tasklet but for our example we will keep it simple. This script will be executed from our context.xml file as shown in the following sections.
initial-query.sql

DROP TABLE PERSON IF EXISTS;

CREATE TABLE PERSON(
firstName VARCHAR(20),
lastName VARCHAR(20),
school VARCHAR(20)
);

INSERT INTO PERSON VALUES('New','User','JavaCodeGeeks');

2.5 Set-Up POJOs

Now to map the data read from the HSQL database table, we will need a POJO and a RowMapper for it. These are two simple java classes.
Person.java

package com.javacodegeeks.example.util;

public class Person {

	private String firstName;
	private String lastName;
	private String school;
	
	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getSchool() {
		return school;
	}
	public void setSchool(String school) {
		this.school = school;
	}
	
	@Override
	public String toString(){
		return "Hello! "+ firstName+" "+lastName+", welcome to "+ school+".";
	}
	
}

PersonMapper.java

package com.javacodegeeks.example.util;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;
public class PersonMapper implements RowMapper{

	public Person mapRow(ResultSet rs, int rowNum) throws SQLException {
		Person person = new Person();
		person.setFirstName(rs.getString("firstName"));
		person.setLastName(rs.getString("lastName"));
		person.setSchool(rs.getString("school"));
		return person;
	}

}

2.6 Define Tasklet

Next we are going to define our Tasklet. It is again a simple class that implements the Tasklet interface.

package com.javacodegeeks.example.util;

import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

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.jdbc.core.JdbcTemplate;

public class MyTasklet implements Tasklet{
	private DataSource dataSource;
	private String sql = "select firstName,lastName,school from PERSON;";
    
	public RepeatStatus execute(StepContribution step, ChunkContext chunk)
			throws Exception {
		List person = new ArrayList();
		JdbcTemplate myTemplate = new JdbcTemplate(getDataSource());
		person = myTemplate.query(sql, new PersonMapper());
		
		for(Person p: person){			
			System.out.println(p);
		}
		return RepeatStatus.FINISHED;
	}
	public DataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}	

}

2.7 Configure Scheduler

Almost there! Ok, so now we write our Scheduler. It extends the QuartzJobBean class. Now this class has a property jobDataAsMap which is a Map through which properties can be supplied into this class. We will keep it minimal and just supply the jobName, jobLauncher and the jobLocator to it as can be seen from the configuration in the job-config.xml file in the following sections. The job will be launched from it based on the cron expression supplied.
MyTaskScheduler.java

package com.javacodegeeks.example.util;
import java.util.Map;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class MyTaskScheduler extends QuartzJobBean{
	private String jobName;
	private JobLauncher jobLauncher;
	private JobLocator jobLocator;
	
	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 context)
			throws JobExecutionException {
		@SuppressWarnings("unchecked")
		Map mapData = context.getMergedJobDataMap();
		
		jobName = (String) mapData.get("jobName");
		
		try{			
			
			JobExecution execution = jobLauncher.run(jobLocator.getJob(jobName), new JobParameters());
			System.out.println("Execution Status: "+ execution.getStatus());
		}catch(Exception e){
			System.out.println("Encountered job execution exception! ");
			e.printStackTrace();
		}
		
	}

}

2.8 Set-Up Context

Under src/main/resources/META-INF/spring, we will add a context.xml file with the following content. Here the generic beans required for setting up the context will be configured. Notice the creation of the meta-data tables and the execution of the initial-query.sql
context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"		
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"	
	xsi:schemaLocation="http://www.springframework.org/schema/beans	
	http://www.springframework.org/schema/beans/spring-beans.xsd 	
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/jdbc 
	http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
    
    <bean id="jobRepository"
	class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="transactionManager" ref="transactionManager" />
		<property name="databaseType" value="hsql" />
  	</bean>
  	
  	<bean id="jobLauncher"
	class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
		<property name="jobRepository" ref="jobRepository" />
  	</bean>
  	
    <bean id="transactionManager"
	class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
	
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		lazy-init="true" destroy-method="close">
		<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
		<property name="url"
			value="jdbc:hsqldb:file:src/main/resources/hsqldb/batchcore.db;shutdown=true;" />		
		<property name="username" value="sa" />
		<property name="password" value="" />

	</bean>
	
	<!-- Create meta-tables -->
	<jdbc:initialize-database data-source="dataSource">
	    <jdbc:script location="classpath:hsqldb/initial-query.sql" />
		<jdbc:script location="org/springframework/batch/core/schema-drop-hsqldb.sql" />
		<jdbc:script location="org/springframework/batch/core/schema-hsqldb.sql" />
    </jdbc:initialize-database>
	
</beans>

2.9 Set-up Job

Next, in job-config.xml, we will configure a Job with a Tasklet in it that reads from the HSQL database table. Note the use of Spring’s SchedulerFactoryBean and Quartz’s JobDetailBean. Our TaskScheduler has been supplied to the latter. Also, the jobRegistry needs to be set-up so that the jobLocator could find the configured jobs.

<?xml version="1.0" encoding="UTF-8"?>
<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"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"	
	xsi:schemaLocation="http://www.springframework.org/schema/beans	
	http://www.springframework.org/schema/beans/spring-beans.xsd 	
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/batch 
    http://www.springframework.org/schema/batch/spring-batch.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd 			
    http://www.springframework.org/schema/jdbc 
	http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
	
	<job id="myJob" xmlns="http://www.springframework.org/schema/batch" restartable="true">
		<step id="step1" allow-start-if-complete="true">
			<tasklet ref="myTasklet">
			</tasklet>
		</step>
	</job>
	
	<bean id="myTasklet" class="com.javacodegeeks.example.util.MyTasklet">
	   <property name="dataSource" ref="dataSource"></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.javacodegeeks.example.util.MyTaskScheduler"></property>
	  	<property name="jobDataAsMap">
	  		<map>
	  			<entry key="jobName" value="myJob"></entry>
	  			<entry key="jobLauncher" value-ref="jobLauncher"></entry>
	  			<entry key="jobLocator" value-ref="jobRegistry"></entry>
	  		</map>
	  	</property>
	  </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" />

</beans>

2.10 Run the Job

Now, in the Main.java, we will just load the context and run it as a Java Application. The Scheduler will take care of running the tasklet.
Main.java

package com.javacodegeeks.example.app;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
	 public static void main(String[] args) {
		 String[] str = {"classpath:META-INF/spring/context.xml","classpath:META-INF/spring/job-config.xml"};
		ApplicationContext ctx = new ClassPathXmlApplicationContext(str);		
	}
}

2.11 Output

On running the application, it will print an output as follows every 10 seconds. Since this is the time set in our cron expression above. Note: the output printed from our Tasklet is “Hello! New User, welcome to JavaCodeGeeks.”

Jul 2, 2015 12:10:10 AM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO: Executing step: [step1]
Hello! New User, welcome to JavaCodeGeeks.
Jul 2, 2015 12:10:10 AM org.springframework.batch.core.launch.support.SimpleJobLauncher run
INFO: Job: [FlowJob: [name=myJob]] completed with the following parameters: [{}] and the following status: [COMPLETED]
Execution Status: COMPLETED
Jul 2, 2015 12:10:20 AM org.springframework.batch.core.launch.support.SimpleJobLauncher run
INFO: Job: [FlowJob: [name=myJob]] launched with the following parameters: [{}]
Jul 2, 2015 12:10:20 AM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO: Executing step: [step1]
Hello! New User, welcome to JavaCodeGeeks.
Execution Status: COMPLETED

3. Conclusion

This brings us to the end of the example. It was a pretty simple example with a pretty simple Tasklet that just read a table record and printed it out. Of course, much more meaningful tasks can be accomplished. The idea was just to demonstrate how to go about scheduling a Spring Batch Job using a Quartz Scheduler. The full example code is available for download below.

Download
You can download the full source code of this example here : SpringBatchQuartz

Joormana Brahma

She has done her graduation in Computer Science and Technology from Guwahati, Assam. She is currently working in a small IT Company as a Software Engineer in Hyderabad, India. She is a member of the Architecture team that is involved in development and quite a bit of R&D. She considers learning and sharing what has been learnt to be a rewarding experience.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button