Quartz

Java Quartz vs Spring Batch Example

1. Introduction

This example shows how to build a Quartz scheduler and Spring Batch application. Both applications execute a business task repeatedly in a different way.

Quartz is an open source library designed to schedule a job for enterprises. Quartz application repeatedly executes the job based on the scheduled time.

Spring Batch is an open source framework designed to enable the development of robust batch applications vital for the daily operations of enterprise systems.  Spring Batch application repeatedly executes the job for the batch data.

2. Business Problem

A telecommunication company receives Call Detail Records (CDRs) from the network switch periodically. An application reads the CDR, enriches it by adding the billing account, and then saves the enriched CDR.

2.1 Business domain models and services

There are two simplified domain models to represent the CDR with simple and complex format.

2.1.1 CallDetailRecord_Simple

Simple CDR model.

CallDetailRecord_Simple

package jcg.demo.model;

public class CallDetailRecord_Simple {

	private String callerId;
	private String calling;

	public void setCallerId(String callerId) {
		this.callerId = callerId;
	}

	public String getCalling() {
		return calling;
	}

	public void setCalling(String calling) {
		this.calling = calling;
	}

	public String getCallerId() {
		return callerId;
	}

	@Override
	public String toString() {
		return "CallDetailRecord_Simple [callerId=" + callerId + ", calling=" + calling + "]";
	}
}

2.1.2 CallDetailRecord_Rich

Rich CDR model.

CallDetailRecord_Rich

package jcg.demo.model;

public class CallDetailRecord_Rich {

	private String callerId;
	private String calling;
	private String billingAccount;

	public void setCallerId(String callerId) {
		this.callerId = callerId;
	}

	public String getCalling() {
		return calling;
	}

	public void setCalling(String calling) {
		this.calling = calling;
	}

	public String getCallerId() {
		return callerId;
	}

	public String getBillingAccount() {
		return billingAccount;
	}

	public void setBillingAccount(String billingAccount) {
		this.billingAccount = billingAccount;
	}

	@Override
	public String toString() {
		return "CallDetailRecord_Rich [ billingAccount=" + billingAccount + ", callerId=" + callerId + ", calling=" + calling + "]";
	}
}

2.2 Call Detail Record service

A service to enrich and save the Call Detail Record.

CallDetailRecordService

package jcg.demo.service;

import org.springframework.stereotype.Service;

import jcg.demo.model.CallDetailRecord_Rich;
import jcg.demo.model.CallDetailRecord_Simple;

/**
 * This service enriches the CallDetailRecord with the billing account.
 * 
 * @author Mary.Zheng
 */
@Service
public class CallDetailRecordService {
	public CallDetailRecord_Rich enrich(CallDetailRecord_Simple simpleCDR) {
		System.out.println("\tCallDetailRecordService enrich()");
		CallDetailRecord_Rich richCdr = new CallDetailRecord_Rich();

		richCdr.setCallerId(simpleCDR.getCallerId());
		richCdr.setCalling(simpleCDR.getCalling());
		richCdr.setBillingAccount("BTN" + simpleCDR.getCallerId());

		return richCdr;
	}

	public void save(CallDetailRecord_Rich richCdr) {
		System.out.println("\tCallDetailRecordService save()");
	}

}

3. Technologies used

The example code in this article was built and run using:

  • Java 1.8.101 (1.8.x will do fine)
  • Maven 3.3.9 (3.3.x will do fine)
  • Quartz 2.2.1 (2.x will do fine)
  • Spring Batch 3.0.5.RELEASE (4.0.0.M1 will do fine)
  • Eclipse Neon (Any Java IDE would work)

4. Quartz Application

4.1 Overview

This example demonstrates how to build an application to process the CDR via Quartz scheduler library.

4.2 Build a Quartz scheduler application

4.2.1 Dependency

Add Quartz dependency to the pom.xml.

Dependency in pom.xml

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

4.2.2 Quartz Job

Create a Quartz job by implementing org.quartz.Job.

QuartzJob

package jcg.demo.scheduler.quartz2;

import java.time.LocalDateTime;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import jcg.demo.model.CallDetailRecord_Rich;
import jcg.demo.model.CallDetailRecord_Simple;
import jcg.demo.service.CallDetailRecordService;

/**
 * This class implements Quartz Job interface, anything you wish to be executed
 * by the Quartz Scheduler should be here it should invokes business class to
 * perform task.
 * 
 * @author Mary.Zheng
 *
 */
public class QuartzJob implements Job {

	private CallDetailRecordService cdrService = new CallDetailRecordService();

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		LocalDateTime localTime = LocalDateTime.now();
		System.out.println(Thread.currentThread().getName() + ": Run QuartzJob at " + localTime.toString());

		CallDetailRecord_Simple simpleCDR = dummySimpleCDR();
		CallDetailRecord_Rich richCdr = cdrService.enrich(simpleCDR);
		cdrService.save(richCdr);
	}

	private CallDetailRecord_Simple dummySimpleCDR() {
		CallDetailRecord_Simple cdr = new CallDetailRecord_Simple();
		cdr.setCalling("3145791111");
		cdr.setCallerId("6365272222");

		return cdr;
	}

}
  • line 21: Create QuartzJob which implements org.quartz.Job
  • line 23: Initialize cdrService from CallDetailRecordService
  • line 31: Invoke cdrService save. This indicates that the data transaction happens at the record level

4.2.3 Quartz application

Create a Quartz scheduler to execute the QuartzJob every minute.

QuartzSchedulerApp

package jcg.demo.scheduler.quartz2;

import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

/**
 * This application schedule a job to run every minute
 * 
 * @author Mary.Zheng
 *
 */
public class QuartzSchedulerApp {

	private static final String TRIGGER_NAME = "MyTriggerName";
	private static final String GROUP = "simple_Group";
	private static final String JOB_NAME = "someJob";
	private static Scheduler scheduler;

	public static void main(String[] args) throws Exception {
		System.out.println("QuartzSchedulerApp main thread: " + Thread.currentThread().getName());

		scheduler = new StdSchedulerFactory().getScheduler();
		scheduler.start();

		Trigger trigger =  buildCronSchedulerTrigger();
		scheduleJob(trigger);

	}

	private static void scheduleJob(Trigger trigger) throws Exception {

		JobDetail someJobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity(JOB_NAME, GROUP).build();

		scheduler.scheduleJob(someJobDetail, trigger);

	}

	private static Trigger buildCronSchedulerTrigger() {
		String CRON_EXPRESSION = "0 * * * * ?";
		Trigger trigger = TriggerBuilder.newTrigger().withIdentity(TRIGGER_NAME, GROUP)
				.withSchedule(CronScheduleBuilder.cronSchedule(CRON_EXPRESSION)).build();

		return trigger;
	}
}

4.3 Quartz scheduler execution

Execute the Quartz scheduler application.

Output

QuartzSchedulerApp main thread: main
DefaultQuartzScheduler_Worker-1: Run QuartzJob at 2017-11-26T11:24:00.049
	CallDetailRecordService enrich()
	CallDetailRecordService save()
DefaultQuartzScheduler_Worker-2: Run QuartzJob at 2017-11-26T11:25:00.002
	CallDetailRecordService enrich()
	CallDetailRecordService save()
DefaultQuartzScheduler_Worker-3: Run QuartzJob at 2017-11-26T11:26:00.003
	CallDetailRecordService enrich()
	CallDetailRecordService save()
...

As you see here, the job ran infinitely every minute. The CallDetailRecordService save() was invoked for each CDR.

5. Spring Batch Application

5.1 Overview

This example demonstrates how to build a batch application to process a CDR via Spring Batch framework.

5.2 Build a Spring batch application

5.2.1 Dependency

Add Spring Batch dependency to the pom.xml.

Dependency in pom.xml

<dependency>
	<groupId>org.springframework.batch</groupId>
	<artifactId>spring-batch-core</artifactId>
	<version>3.0.5.RELEASE</version>
</dependency>

5.2.2 Spring Batch ItemReader

Create a BatchItemReader by implementing the org.springframework.batch.item.ItemReader.

BatchItemReader

package jcg.demo.batch.spring;

import java.util.Random;

import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.stereotype.Component;

import jcg.demo.model.CallDetailRecord_Simple;

/**
 * This is implementation of the batch item reader according to your business.
 * This example builds a dummy data. Spring-batch provides several common
 * reader. such as FlatFileItemReader, JdbcCursorItemReader etc. The batch
 * job completes when the ItemReader has no data to read.
 * 
 * @author Mary.Zheng
 *
 */
@Component("batchItemReader")
public class BatchItemReader implements ItemReader <CallDetailRecord_Simple > {

	@Override
	public CallDetailRecord_Simple read()
			throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {

		CallDetailRecord_Simple cdr = buildDummyCDR();
		System.out.println(Thread.currentThread().getName() + "- Inside BatchItemReader..." + cdr);
		return cdr;
	}

	private CallDetailRecord_Simple buildDummyCDR() {

		Random rand = new Random();
		int value = rand.nextInt(10);

		if (value == 0) {
			return null;
		}

		CallDetailRecord_Simple cdr = new CallDetailRecord_Simple();

		cdr.setCalling("314579111" + value);
		cdr.setCallerId("636527222" + value);

		return cdr;
           }
  • line 23: Create BatchItemReader which implements org.springframework.batch.item.ItemReader with CallDetailRecord_Simple
  • line 39-41: Batch job is completed when its step’s reader reads a null object

5.2.3 Spring Batch ItemWriter

Create BatchItemWriter by implementing the org.springframework.batch.item.ItemWriter.

BatchItemWriter

package jcg.demo.batch.spring;

import java.util.List;

import org.springframework.batch.item.ItemWriter;
import org.springframework.stereotype.Component;

import jcg.demo.model.CallDetailRecord_Rich;

/**
 * This is implementation of the batch item writer according to your business.
 * 
 * @author Mary.Zheng
 *
 */
@Component("batchItemWriter")
public class BatchItemWriter implements ItemWriter <CallDetailRecord_Rich >{

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

		System.out.println(Thread.currentThread().getName() + "- Inside BatchItemWriter..." + arg0);
	}

}
  • line 17: Create BatchItemWriter which implements org.springframework.batch.item.ItemWriter with CallDetailRecord_Rich

5.2.4 Spring Batch ItemProcessor

Create BatchItemProcessor by implementing the org.springframework.batch.item.ItemProcessor.

BatchItemProcessor

package jcg.demo.batch.spring;

import org.springframework.batch.item.ItemProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import jcg.demo.model.CallDetailRecord_Rich;
import jcg.demo.model.CallDetailRecord_Simple;
import jcg.demo.service.CallDetailRecordService;

/**
 * This is implementation of the batch item processor according to your
 * business. It transforms the batch item from the reader format to the writer
 * format.
 * 
 * @author Mary.Zheng
 *
 */
@Component("batchItemProcesser")
public class BatchItemProcessor implements ItemProcessor <CallDetailRecord_Simple , CallDetailRecord_Rich> {

	@Autowired
	private CallDetailRecordService cdrService;

	@Override
	public CallDetailRecord_Rich process(CallDetailRecord_Simple cdr) throws Exception {
		System.out.println(Thread.currentThread().getName() + "- Inside BatchItemProcessor..." + cdr);

		return cdrService.enrich(cdr);
	}

}
  • line 20: Create BatchItemProcessor which implements org.springframework.batch.item.ItemProcessor to transform the data from CallDetailRecord_Simple to CallDetailRecord_Rich

5.2.5 Configure Spring Batch beans

Configure Spring Batch beans provided by Spring Batch framework.

batchContext.xml

<?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:task="http://www.springframework.org/schema/task" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:util="http://www.springframework.org/schema/util" xmlns:crypt="http://springcryptoutils.com/schema/crypt"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-3.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
		http://springcryptoutils.com/schema/crypt http://springcryptoutils.com/schema/crypt.xsd
		http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd">

	<context:component-scan base-package="jcg.demo.batch.spring,jcg.demo.service" />

	<batch:job id="processCDRJob" xmlns="http://www.springframework.org/schema/batch">
		<batch:step id="step1">
			<batch:tasklet>
				<batch:chunk reader="batchItemReader" writer="batchItemWriter"
					processor="batchItemProcesser" commit-interval="1" />
			</batch:tasklet>
		</batch:step>
	</batch:job>

	<bean id="transactionManager"
		class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />

	<bean id="jobRepository"
		class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
		<property name="transactionManager" ref="transactionManager" />
	</bean>

	<bean id="jobLauncher"
		class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
		<property name="jobRepository" ref="jobRepository" />
	</bean>

</beans>
  • line 16: Enable Spring ComponentScan at both jcg.demo.batch.spring and jcg.demo.service packages
  • line 18-25: Configure the processCDRJob batch job based on batchItemReader created at step 5.2.2, batchItemWriter created at step 5.2.3, and batchItemProcessor created at step 5.2.4
  • line 27-28: Configure Spring Batch Bean transactionManager
  • line 30-33: Configure Spring Batch Bean jobRepository
  • line 35-38: Configure Spring Batch Bean jobLauncher

5.2.6 Spring Batch application

Create a Spring Batch application.

SpringBatchApp

package jcg.demo.batch.spring;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@ImportResource({ "classpath:/spring/batchcontext.xml" })
public class SpringBatchApp {

	public static void main(String[] args) throws JobExecutionAlreadyRunningException, JobRestartException,
			JobInstanceAlreadyCompleteException, JobParametersInvalidException {

		@SuppressWarnings("resource")
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.register(SpringBatchApp.class);
		context.refresh();

		JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");

		Job processCDRJob = (Job) context.getBean("processCDRJob");

		JobExecution execution = jobLauncher.run(processCDRJob, new JobParameters());
		System.out.println(Thread.currentThread().getName() + "- Exit Status : " + execution.getStatus());

	}

}
  • line 16: Import Spring batchcontext.xml created at step 5.2.5
  • line 27: Find the JobLauncher from Spring context
  • line 29: Find the processCDRJob from Spring context
  • line 31: Invoke jobLauncher run method to processCDRJob
  • line 32: Print out the job execution status

5.3 Spring batch application execution

Execute the Spring batch application.

Output

Nov 26, 2017 10:29:56 AM org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2e5d6d97: startup date [Sun Nov 26 10:29:56 CST 2017]; root of context hierarchy
Nov 26, 2017 10:29:56 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring/batchcontext.xml]
Nov 26, 2017 10:29:57 AM org.springframework.batch.core.launch.support.SimpleJobLauncher afterPropertiesSet
INFO: No TaskExecutor has been set, defaulting to synchronous executor.
Nov 26, 2017 10:29:57 AM org.springframework.batch.core.launch.support.SimpleJobLauncher run
INFO: Job: [FlowJob: [name=processCDRJob]] launched with the following parameters: [{}]
Nov 26, 2017 10:29:57 AM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO: Executing step: [step1]
main- Inside BatchItemReader...CallDetailRecord_Simple [callerId=6365272224, calling=3145791114]
main- Inside BatchItemProcessor...CallDetailRecord_Simple [callerId=6365272224, calling=3145791114]
	CallDetailRecordService enrich()
main- Inside BatchItemWriter...[CallDetailRecord_Rich [ billingAccount=BTN6365272224, callerId=6365272224, calling=3145791114]]
main- Inside BatchItemReader...CallDetailRecord_Simple [callerId=6365272225, calling=3145791115]
main- Inside BatchItemProcessor...CallDetailRecord_Simple [callerId=6365272225, calling=3145791115]
	CallDetailRecordService enrich()
main- Inside BatchItemWriter...[CallDetailRecord_Rich [ billingAccount=BTN6365272225, callerId=6365272225, calling=3145791115]]
main- Inside BatchItemReader...CallDetailRecord_Simple [callerId=6365272221, calling=3145791111]
main- Inside BatchItemProcessor...CallDetailRecord_Simple [callerId=6365272221, calling=3145791111]
	CallDetailRecordService enrich()
main- Inside BatchItemWriter...[CallDetailRecord_Rich [ billingAccount=BTN6365272221, callerId=6365272221, calling=3145791111]]
main- Inside BatchItemReader...null
Nov 26, 2017 10:29:57 AM org.springframework.batch.core.launch.support.SimpleJobLauncher run
INFO: Job: [FlowJob: [name=processCDRJob]] completed with the following parameters: [{}] and the following status: [COMPLETED]
main- Exit Status : COMPLETED

As you see here, the Spring Batch application was completed with exist status of “COMPLETED” when the Spring Batch ItemReader no longer read any item.

  • line 8: The log provided from Spring batch framework to launch the processCDRJob
  • line 9-10: The log provided from Spring batch framework to execute batch job Step 1
  • line 25: The log provided from Spring batch framework to display the Job status log

6. Comparison

Quartz is a open source solution for scheduling problem. The data transaction is not part of the Quartz library. Application handles the transaction at the individual record level.

Spring Batch is an open source framework solution for a batch/bulk processing paradigm. The data transaction is part of Spring Batch framework. It helps business focus on the batch data’s operations – reading, processing and writing based on the business logic for each batch job. Spring Batch framework covers logging, transaction, starting, stopping, execution the job.

7. Summary

In this example, we demonstrate how to build Quartz scheduler and Spring Batch application. Both applications execute a specific business task repeatedly in a different way. In a nutshell, Quartz helps businesses decide when to execute the job. Spring Batch helps businesses decide what to include in a batch job’s step.

8. Download the Source Code

This example consists of both Quartz scheduler and Spring Batch application.

Download
You can download the full source code of this example here:Java Quartz vs Spring Batch Example

Mary Zheng

Mary has graduated from Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She works as a senior Software Engineer in the telecommunications sector where she acts as a leader and works with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

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

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
arisone
arisone
6 years ago

Hi Mary nice article to introduce the problem of tasks schedule and run. But I disagree with the “vs” in the title because Quartz and Spring Batch are complementary about the problem. One is a scheduler (maybe the de facto standard) the other is a batch framework to build up your job engine. Quartz tells when execute something and if that thing is a job maybe it could be a Spring Batch job. See also https://projects.spring.io/spring-batch/faq.html#quartz

Dumitru
Dumitru
1 year ago
Reply to  arisone

Yes, I also agree. I could be Quartz vs @Scheduled from Spring.

Back to top button