0

0

HOW-TO: Quartz Scheduler with Clustering in JEE application_MySQL

php中文网

php中文网

发布时间:2016-06-01 13:14:03

|

3356人浏览过

|

来源于php中文网

原创

quartz scheduler is one of the most popular scheduling library in java world. i had worked with quartz mostly in spring applications in the past. recently, i have been investigating scheduling in jee 6 application running on jboss 7.1.1 that is going to be deployed in the cloud. as one of the options i consider is quartz scheduler as it offers clustering with database. in this article i will show how easy is to configure quartz in jee application and run it either on jboss 7.1.1 or wildfly 8.0.0, use mysql as job store and utilize cdi to use dependency injection in jobs. all will be done in intellij. let’s get started.

Create Maven project

I usedorg.codehaus.mojo.archetypes:webapp-javaee6archetype to bootstrap the application and then I slightly modified thepom.xml. I also addedslf4Jdependency, so the resultingpom.xmllooks as following:

	4.0.0	pl.codeleak	quartz-jee-demo	1.0	war	quartz-jee-demo			${project.build.directory}/endorsed		UTF-8							javax			javaee-api			6.0			provided									org.slf4j			slf4j-api			1.7.7							org.slf4j			slf4j-jdk14			1.7.7													org.apache.maven.plugins				maven-compiler-plugin				2.3.2									1.7					1.7											${endorsed.dir}																			org.apache.maven.plugins				maven-war-plugin				2.1.1									false														org.apache.maven.plugins				maven-dependency-plugin				2.1															validate													copy																			${endorsed.dir}							true																								javax									javaee-endorsed-api									6.0									jar																																				

The next thing was to import the project to IDE. In my case this is IntelliJ and create a run configuration with JBoss 7.1.1.

One note, in the VM Options in run configuration I added two variables:

-Djboss.server.default.config=standalone-custom.xml-Djboss.socket.binding.port-offset=100

quartz-scheduler-with-clustering-img1

standalone-custom.xmlis a copy of the standardstandalone.xml, as the configuration will need to be modified (see below).

Configure JBoss server

In my demo application I wanted to use MySQL database with Quartz, so I needed to add MySQL data source to my configuration. This can be quickly done with two steps.

Add Driver Module

I created a folderJBOSS_HOME/modules/com/mysql/main. In this folder I added two files:module.xmlandmysql-connector-java-5.1.23.jar. The module file looks as follows:

Configure Data Source

In thestandalone-custom.xmlfile in thedatasourcessubsystem I added a new data source:

 jdbc:mysql://localhost:3306/javaee com.mysql jeeuserpass 

And the driver:

 

Note: For the purpose of this demo, the data source is not JTA managed to simplify the configuration.

Configure Quartz with Clustering

I used official tutorial to configure Quarts with Clustering:http://quartz-scheduler.org/documentation/quartz-2.2.x/configuration/ConfigJDBCJobStoreClustering

Add Quartz dependencies topom.xml

	org.quartz-scheduler	quartz	2.2.1	org.quartz-scheduler	quartz-jobs	2.2.1

Addquartz.propertiestosrc/main/resources

#============================================================================# Configure Main Scheduler Properties#============================================================================org.quartz.scheduler.instanceName = MySchedulerorg.quartz.scheduler.instanceId = AUTO#============================================================================# Configure ThreadPool#============================================================================org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount = 1#============================================================================# Configure JobStore#============================================================================org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegateorg.quartz.jobStore.useProperties = falseorg.quartz.jobStore.dataSource=MySqlDSorg.quartz.jobStore.isClustered = trueorg.quartz.jobStore.clusterCheckinInterval = 5000org.quartz.dataSource.MySqlDS.jndiURL=java:jboss/datasources/MySqlDS

Create MySQL tables to be used by Quartz

The schema file can be found in the Quartz distribution:quartz-2.2.1/docs/dbTables.

Demo code

Having the configuration in place, I wanted to check if Quartz works, so I created a scheduler, with no jobs and triggers.

package pl.codeleak.quartzdemo;import org.quartz.JobKey;import org.quartz.Scheduler;import org.quartz.SchedulerException;import org.quartz.TriggerKey;import org.quartz.impl.StdSchedulerFactory;import org.quartz.impl.matchers.GroupMatcher;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.annotation.PostConstruct;import javax.annotation.PreDestroy;import javax.ejb.Singleton;import javax.ejb.Startup;@Startup@Singletonpublic class SchedulerBean {	private Logger LOG = LoggerFactory.getLogger(SchedulerBean.class);	private Scheduler scheduler;	@PostConstruct	public void scheduleJobs() {		try {			scheduler = new StdSchedulerFactory().getScheduler();						scheduler.start();			printJobsAndTriggers(scheduler);		} catch (SchedulerException e) {		 LOG.error("Error while creating scheduler", e);		}	}	private void printJobsAndTriggers(Scheduler scheduler) throws SchedulerException {		LOG.info("Quartz Scheduler: {}", scheduler.getSchedulerName());		for(String group: scheduler.getJobGroupNames()) {			for(JobKey jobKey : scheduler.getJobKeys(GroupMatcher.groupEquals(group))) {				LOG.info("Found job identified by {}", jobKey);			}		}		for(String group: scheduler.getTriggerGroupNames()) {			for(TriggerKey triggerKey : scheduler.getTriggerKeys(GroupMatcher.groupEquals(group))) {				LOG.info("Found trigger identified by {}", triggerKey);			}		}	}	@PreDestroy	public void stopJobs() {		if (scheduler != null) {			try {				scheduler.shutdown(false);			} catch (SchedulerException e) {				LOG.error("Error while closing scheduler", e);			}		}	}}

When you run the application you should be able to see some debugging information from Quartz:

Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.NOT STARTED.Currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is clustered.

Let Quartz utilize CDI

In Quartz, jobs must implementorg.quartz.Jobinterface.

package pl.codeleak.quartzdemo;import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;public class SimpleJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {// do something}}

Then to create a Job we use JobBuilder:

JobKey job1Key = JobKey.jobKey("job1", "my-jobs");JobDetail job1 = JobBuilder.newJob(SimpleJob.class).withIdentity(job1Key).build();

In my example, I needed to inject EJBs to my jobs in order to re-use existing application logic. So in fact, I needed to inject a EJB reference. How this can be done with Quartz? Easy. Quartz Scheduler has a method to provide JobFactory to that will be responsible for creating Job instances.

package pl.codeleak.quartzdemo;import org.quartz.Job;import org.quartz.JobDetail;import org.quartz.Scheduler;import org.quartz.SchedulerException;import org.quartz.spi.JobFactory;import org.quartz.spi.TriggerFiredBundle;import javax.enterprise.inject.Any;import javax.enterprise.inject.Instance;import javax.inject.Inject;import javax.inject.Named;public class CdiJobFactory implements JobFactory {	@Inject	@Any	private Instance jobs;	@Override	public Job newJob(TriggerFiredBundle triggerFiredBundle, Scheduler scheduler) throws SchedulerException {		final JobDetail jobDetail = triggerFiredBundle.getJobDetail();		final Class jobClass = jobDetail.getJobClass();		for (Job job : jobs) {			if (job.getClass().isAssignableFrom(jobClass)) {				return job;			}		}		throw new RuntimeException("Cannot create a Job of type " + jobClass);	}}

As of now, all jobs can use dependency injection and inject other dependencies, including EJBs.

package pl.codeleak.quartzdemo.ejb;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.ejb.Stateless;@Statelesspublic class SimpleEjb {		private static final Logger LOG = LoggerFactory.getLogger(SimpleEjb.class);		public void doSomething() {		LOG.info("Inside an EJB");	}}package pl.codeleak.quartzdemo;import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;import pl.codeleak.quartzdemo.ejb.SimpleEjb;import javax.ejb.EJB;import javax.inject.Named;public class SimpleJob implements Job {	@EJB // @Inject will work too	private SimpleEjb simpleEjb;	@Override	public void execute(JobExecutionContext context) throws JobExecutionException {		simpleEjb.doSomething();	}}

The last step is to modify SchedulerBean:

package pl.codeleak.quartzdemo;import org.quartz.*;import org.quartz.impl.StdSchedulerFactory;import org.quartz.impl.matchers.GroupMatcher;import org.quartz.spi.JobFactory;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.annotation.PostConstruct;import javax.annotation.PreDestroy;import javax.ejb.Singleton;import javax.ejb.Startup;import javax.inject.Inject;@Startup@Singletonpublic class SchedulerBean {	private Logger LOG = LoggerFactory.getLogger(SchedulerBean.class);	private Scheduler scheduler;	@Inject	private JobFactory cdiJobFactory;	@PostConstruct	public void scheduleJobs() {		try {			scheduler = new StdSchedulerFactory().getScheduler();			scheduler.setJobFactory(cdiJobFactory);			JobKey job1Key = JobKey.jobKey("job1", "my-jobs");			JobDetail job1 = JobBuilder					.newJob(SimpleJob.class)					.withIdentity(job1Key)					.build();			TriggerKey tk1 = TriggerKey.triggerKey("trigger1", "my-jobs");			Trigger trigger1 = TriggerBuilder					.newTrigger()					.withIdentity(tk1)					.startNow()					.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10))					.build();			scheduler.scheduleJob(job1, trigger1);			scheduler.start();			printJobsAndTriggers(scheduler);		} catch (SchedulerException e) {			LOG.error("Error while creating scheduler", e);		}	}	private void printJobsAndTriggers(Scheduler scheduler) throws SchedulerException {		// not changed	}	@PreDestroy	public void stopJobs() {		// not changed	}}

Note: Before running the application add beans.xml file to WEB-INF directory.

You can now start the server and observe the results. Firstly, job and trigger was created:

剪映专业版
剪映专业版

一款全能易用的桌面端剪辑软件

下载
12:08:19,592 INFO (MSC service thread 1-3) Quartz Scheduler: MyScheduler12:08:19,612 INFO (MSC service thread 1-3) Found job identified by my-jobs.job112:08:19,616 INFO (MSC service thread 1-3) Found trigger identified by m

Our job is running (at about every 10 seconds):

12:08:29,148 INFO (MyScheduler_Worker-1) Inside an EJB12:08:39,165 INFO (MyScheduler_Worker-1) Inside an EJB

Look also inside the Quartz tables, and you will see it is filled in with the data.

Test the application

The last thing I wanted to check was how the jobs are triggered in multiple instances. For my test, I just cloned the server configuration twice in IntelliJ and assigned different port offset to each new copy.

quartz-scheduler-with-clustering-img2
Additional change I needed to do is to modify the creation of jobs and triggers. Since all Quartz objects are stored in the database, creating the same job and trigger (with the same keys) will cause an exception to be raised:

Error while creating scheduler: org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'my-jobs.job1', because one already exists with this identification.

I needed to change the code, to make sure that if the job/trigger exists I update it. The final code of the scheduleJobs method for this test registers three triggers for the same job.

@PostConstructpublic void scheduleJobs() {try {	scheduler = new StdSchedulerFactory().getScheduler();	scheduler.setJobFactory(cdiJobFactory);	JobKey job1Key = JobKey.jobKey("job1", "my-jobs");	JobDetail job1 = JobBuilder		.newJob(SimpleJob.class)		.withIdentity(job1Key)		.build();	TriggerKey tk1 = TriggerKey.triggerKey("trigger1", "my-jobs");	Trigger trigger1 = TriggerBuilder		.newTrigger()		.withIdentity(tk1)		.startNow()		.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10))		.build();	TriggerKey tk2 = TriggerKey.triggerKey("trigger2", "my-jobs");	Trigger trigger2 = TriggerBuilder		.newTrigger()		.withIdentity(tk2)		.startNow()		.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10))		.build();	TriggerKey tk3 = TriggerKey.triggerKey("trigger3", "my-jobs");	Trigger trigger3 = TriggerBuilder		.newTrigger()		.withIdentity(tk3)		.startNow()		.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10))		.build();	scheduler.scheduleJob(job1, newHashSet(trigger1, trigger2, trigger3), true);	scheduler.start();	printJobsAndTriggers(scheduler);} catch (SchedulerException e) {	LOG.error("Error while creating scheduler", e);}}

In addition to the above, I added logging the JobExecutionContext in SimpleJob, so I could better analyze the outcome.

@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {try {	LOG.info("Instance: {}, Trigger: {}, Fired at: {}",		context.getScheduler().getSchedulerInstanceId(),		context.getTrigger().getKey(),		sdf.format(context.getFireTime()));} catch (SchedulerException e) {}simpleEjb.doSomething();}

After running all three server instances I observed the results.

quartz-scheduler-with-clustering-img3

Job execution

I observed trigger2 execution on all three nodes, and it was executed on three of them like this:

Instance: kolorobot1399805959393 (instance1), Trigger: my-jobs.trigger2, Fired at: 13:00:09Instance: kolorobot1399805989333 (instance3), Trigger: my-jobs.trigger2, Fired at: 13:00:19Instance: kolorobot1399805963359 (instance2), Trigger: my-jobs.trigger2, Fired at: 13:00:29Instance: kolorobot1399805959393 (instance1), Trigger: my-jobs.trigger2, Fired at: 13:00:39Instance: kolorobot1399805959393 (instance1), Trigger: my-jobs.trigger2, Fired at: 13:00:59

Similarly for other triggers.

Recovery

After I disconnected kolorobot1399805989333 (instance3), after some time I saw the following in the logs:

ClusterManager: detected 1 failed or restarted instances.ClusterManager: Scanning for instance "kolorobot1399805989333"'s failed in-progress jobs.

Then I disconnected kolorobot1399805963359 (instance2) and again this is what I saw in the logs:

ClusterManager: detected 1 failed or restarted instances.ClusterManager: Scanning for instance "kolorobot1399805963359"'s failed in-progress jobs.ClusterManager: ......Freed 1 acquired trigger(s).

As of now all triggers where executed by kolorobot1399805959393 (instance1)

Running on Wildfly 8

Without any change I could deploy the same application on WildFly 8.0.0. Similarly to JBoss 7.1.1 I added MySQL module (the location of modules folder is different on WildFly 8 –modules/system/layers/base/com/mysql/main. The datasource and the driver was defined exactly the same as shown above. I created a run configuration for WildFly 8:

quartz-scheduler-with-clustering-img4
And I ran the application getting the same results as with JBoss 7.

I found out the WildFly seem to offer adatabase based store for persistent EJB timers, but I did not investigate it yet. Maybe something for another blog post.

Source code

  • Please find the source code for this blog post on GitHub:https://github.com/kolorobot/quartz-jee-demo
Reference: HOW-TO: Quartz Scheduler with Clustering in JEE application with MySQLfrom ourJCG partnerRafal Borowiec at theCodeleak.plblog.

You might also like:
  • Getting started with Quartz Scheduler on MySQL database
  • Quartz 2 Scheduler example
  • Quartz scheduler plugins – hidden treasure
Related Whitepaper:

HOW-TO: Quartz Scheduler with Clustering in JEE application_MySQL

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now! 

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
全国统一发票查询平台入口合集
全国统一发票查询平台入口合集

本专题整合了全国统一发票查询入口地址合集,阅读专题下面的文章了解更多详细入口。

19

2026.02.03

短剧入口地址汇总
短剧入口地址汇总

本专题整合了短剧app推荐平台,阅读专题下面的文章了解更多详细入口。

27

2026.02.03

植物大战僵尸版本入口地址汇总
植物大战僵尸版本入口地址汇总

本专题整合了植物大战僵尸版本入口地址汇总,前往文章中寻找想要的答案。

15

2026.02.03

c语言中/相关合集
c语言中/相关合集

本专题整合了c语言中/的用法、含义解释。阅读专题下面的文章了解更多详细内容。

3

2026.02.03

漫蛙漫画网页版入口与正版在线阅读 漫蛙MANWA官网访问专题
漫蛙漫画网页版入口与正版在线阅读 漫蛙MANWA官网访问专题

本专题围绕漫蛙漫画(Manwa / Manwa2)官网网页版入口进行整理,涵盖漫蛙漫画官方主页访问方式、网页版在线阅读入口、台版正版漫画浏览说明及基础使用指引,帮助用户快速进入漫蛙漫画官网,稳定在线阅读正版漫画内容,避免误入非官方页面。

13

2026.02.03

Yandex官网入口与俄罗斯搜索引擎访问指南 Yandex中文登录与网页版入口
Yandex官网入口与俄罗斯搜索引擎访问指南 Yandex中文登录与网页版入口

本专题汇总了俄罗斯知名搜索引擎 Yandex 的官网入口、免登录访问地址、中文登录方法与网页版使用指南,帮助用户稳定访问 Yandex 官网,并提供一站式入口汇总。无论是登录入口还是在线搜索,用户都能快速获取最新稳定的访问链接与使用指南。

114

2026.02.03

Java 设计模式与重构实践
Java 设计模式与重构实践

本专题专注讲解 Java 中常用的设计模式,包括单例模式、工厂模式、观察者模式、策略模式等,并结合代码重构实践,帮助学习者掌握 如何运用设计模式优化代码结构,提高代码的可读性、可维护性和扩展性。通过具体示例,展示设计模式如何解决实际开发中的复杂问题。

3

2026.02.03

C# 并发与异步编程
C# 并发与异步编程

本专题系统讲解 C# 异步编程与并发控制,重点介绍 async 和 await 关键字、Task 类、线程池管理、并发数据结构、死锁与线程安全问题。通过多个实战项目,帮助学习者掌握 如何在 C# 中编写高效的异步代码,提升应用的并发性能与响应速度。

2

2026.02.03

Python 强化学习与深度Q网络(DQN)
Python 强化学习与深度Q网络(DQN)

本专题深入讲解 Python 在强化学习(Reinforcement Learning)中的应用,重点介绍 深度Q网络(DQN) 及其实现方法,涵盖 Q-learning 算法、深度学习与神经网络的结合、环境模拟与奖励机制设计、探索与利用的平衡等。通过构建一个简单的游戏AI,帮助学习者掌握 如何使用 Python 训练智能体在动态环境中作出决策。

3

2026.02.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号