The goal of running a Quartz job in the clustered environment is NOT to have duplicate running jobs. The triggered job should run just one time regardless of the number of nodes in the clustered environment.
- Download Quartz and extract the file.
- Navigate to
quartz-x.x.x -> docs -> dbTables
and run the database SQL script to create the Quartz tables.quartz-x.x.x |- docs |- dbTables |- tables_<database>.sql <- Pick one that matches your database |- images |- examples |- javadoc |- lib |- licenses |- src
- Add the Quartz dependency in
pom.xml
:-<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.0</version> </dependency>
- Create
quartz.properties
file undersrc/main/resources
with the following configuration:-#============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceId = AUTO org.quartz.scheduler.makeSchedulerThreadDaemon = true #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 1 org.quartz.threadPool.makeThreadsDaemons = true #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.MSSQLDelegate org.quartz.jobStore.isClustered = true
Remember to change
org.quartz.jobStore.driverDelegateClass
value to match your database type. In my case, I’m using MS SQL Server. - I want to be able to autowire Spring beans in my Quartz job. So, I created a custom job factory called
AutowiringSpringBeanJobFactory
:-public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private transient AutowireCapableBeanFactory beanFactory; @Override public void setApplicationContext(final ApplicationContext context) { beanFactory = context.getAutowireCapableBeanFactory(); } @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { final Object job = super.createJobInstance(bundle); beanFactory.autowireBean(job); return job; } }
- In my Quartz job, I can reuse my existing Spring bean by autowiring it:-
@Service @DisallowConcurrentExecution public class MyJob implements Job { @Autowired private MyService myService; @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("Message: " + myService.getHelloWorld()); } }
- In this example, I’m creating a Java-based Spring configuration called
QuartzConfig
:-@Configuration public class QuartzConfig { // this data source points to the database that contains Quartz tables @Autowired private DataSource dataSource; @Autowired private PlatformTransactionManager transactionManager; @Autowired private ApplicationContext applicationContext; @Bean public SchedulerFactoryBean quartzScheduler() { SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean(); quartzScheduler.setQuartzProperties(quartzProperties()); quartzScheduler.setDataSource(dataSource); quartzScheduler.setTransactionManager(transactionManager); quartzScheduler.setOverwriteExistingJobs(true); // Custom job factory of spring with DI support for @Autowired AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); quartzScheduler.setJobFactory(jobFactory); Trigger[] triggers = { processMyJobTrigger().getObject() }; quartzScheduler.setTriggers(triggers); return quartzScheduler; } @Bean public JobDetailFactoryBean processMyJob() { JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); jobDetailFactory.setJobClass(MyJob.class); jobDetailFactory.setDurability(true); return jobDetailFactory; } @Bean // Configure cron to fire trigger every 1 minute public CronTriggerFactoryBean processMyJobTrigger() { CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); cronTriggerFactoryBean.setJobDetail(processMyJob().getObject()); cronTriggerFactoryBean.setCronExpression("0 0/1 * * * ?"); return cronTriggerFactoryBean; } @Bean public Properties quartzProperties() { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties")); Properties properties; try { propertiesFactoryBean.afterPropertiesSet(); properties = propertiesFactoryBean.getObject(); } catch (IOException e) { throw new RuntimeException("Unable to load quartz.properties", e); } return properties; } }
Where is the magic allowing for multiple concurrently running nodes to not collide?
The database manages the semaphore to ensure only one running node is executing the triggerred job. If you run this SQL query
select * from dbo.QRTZ_TRIGGERS
, you will see which job is currently running, when it will run again, etc.thank you, it works very good for me