Skip to content

Instantly share code, notes, and snippets.

@jeffsheets
Last active February 12, 2022 14:44
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save jeffsheets/5862630 to your computer and use it in GitHub Desktop.
Save jeffsheets/5862630 to your computer and use it in GitHub Desktop.
Configuring Quartz 2.1.7 with Spring 3.1.3 in clustered mode
/**
* Autowire Quartz Jobs with Spring context dependencies
* @see http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030
*/
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
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;
}
}
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class FirstJob extends QuartzJobBean {
@Autowired
private FirstService firstService;
@Override
protected void executeInternal(JobExecutionContext context) {
//Quartz jobs have not been authenticated with acegi or spring security
//so you may have to setup a user before calling your service methods
//I used SecurityContextHolder.getContext().setAuthentication(quartzUser)
//on an older version of acegi
firstService.updateSomethingInTheDatabase();
}
}
/**
* Needed to set Quartz useProperties=true when using Spring classes,
* because Spring sets an object reference on JobDataMap that is not a String
*
* @see http://site.trimplement.com/using-spring-and-quartz-with-jobstore-properties/
* @see http://forum.springsource.org/showthread.php?130984-Quartz-error-IOException
*/
public class PersistableCronTriggerFactoryBean extends CronTriggerFactoryBean {
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
//Remove the JobDetail element
getJobDataMap().remove(JobDetailAwareTrigger.JOB_DETAIL_KEY);
}
}
# Using Spring datasource in quartzJobsConfig.xml
# Spring uses LocalDataSourceJobStore extension of JobStoreCMT
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
# Change this to match your DB vendor
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate
# Needed to manage cluster instances
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.instanceName=MY_JOB_SCHEDULER
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
/**
* Verifies that the Cron Trigger Time Strings for the jobs are setup correctly
*/
@ContextConfiguration(locations = {"classpath:/applicationContext-test.xml"})
public class QuartzCronTriggerTest extends AbstractTransactionalJUnit4SpringContextTests {
@Autowired
private SchedulerFactoryBean quartzScheduler;
@Test
public void testFirstTrigger() throws SchedulerException {
Trigger firstTrigger = quartzScheduler.getScheduler().getTrigger(new TriggerKey("firstTrigger"));
//Must use tomorrow for testing because jobs have startTime of now
DateTime tomorrow = new DateMidnight().toDateTime().plusDays(1);
//Test first
Date next = firstTrigger.getFireTimeAfter(tomorrow.toDate());
DateTime expected = tomorrow.plusHours(5);
assertThat(next, is(expected.toDate()));
//Test the next day
next = firstTrigger.getFireTimeAfter(next);
expected = expected.plusDays(1);
assertThat(next, is(expected.toDate()));
}
}
<!-- truncated pieces of applicationContext.xml -->
<bean id="firstJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.sheetsj.quartz.job.FirstJob"/>
<property name="durability" value="true"/>
</bean>
<bean id="firstTrigger" class="com.sheetsj.quartz.PersistableCronTriggerFactoryBean">
<property name="jobDetail" ref="firstJobDetail" />
<!-- run every morning at 5:00 AM -->
<property name="cronExpression" value="0 0 5 * * ?" />
</bean>
<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:quartz.properties"/>
<property name="dataSource" value="dataSource"/>
<property name="transactionManager" value="transactionManager"/>
<!-- This name is persisted as SCHED_NAME in db. for local testing could change to unique name
to avoid collision with dev server -->
<property name="schedulerName" value="quartzScheduler"/>
<!-- Will update database cron triggers to what is in this jobs file on each deploy.
Replaces all previous trigger and job data that was in the database. YMMV -->
<property name="overwriteExistingJobs" value="true"/>
<property name="autoStartup" value="true"/>
<property name="applicationContextSchedulerContextKey" value="applicationContext"/>
<property name="jobFactory">
<bean class="com.sheetsj.quartz.AutowiringSpringBeanJobFactory"/>
</property>
<!-- NOTE: Must add both the jobDetail and trigger to the scheduler! -->
<property name="jobDetails">
<list>
<ref bean="firstJobDetail" />
</list>
</property>
<property name="triggers">
<list>
<ref bean="firstTrigger"/>
</list>
</property>
</bean>
@sajeth
Copy link

sajeth commented Jan 21, 2015

can you give bean configurations for transactionManager as well as dataSource?

@jeffsheets
Copy link
Author

My dataSource config for prod uses jndi. Something like this should work for dev though:

   <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
        <property name="url" value="jdbc:sqlserver://SERVERHERE;instanceName=INSTANCEHERE;databaseName=DBHERE" />
        <property name="username" value="USERHERE" />
        <property name="password" value="PASSHERE" />
    </bean>

When running locally I set the transactionManager to simply be the value null. In other environments it is setup by this spring annotation:

<tx:jta-transaction-manager/>

@amberfan
Copy link

<property name="configLocation" value="classpath:quartz.properties"/>
<property name="dataSource" value="dataSource"/>
<property name="transactionManager" value="transactionManager"/>

I think <property name="dataSource" value="dataSource"/> should be <property name="dataSource" ref="dataSource"/>,otherwise an exception will be raised.

@jeffsheets
Copy link
Author

@amberfan good catch. Same is true for transactionManager. In my real project I'm using properties files to hold the value, so it looks like this:

<property name="configLocation" value="classpath:config/${quartz.props.filename}"/>
<property name="dataSource" value="#{${quartz.dataSource}}"/>
<property name="transactionManager" value="#{${quartz.transactionManager}}"/>

but without properties values it should use ref like you mentioned:

<property name="configLocation" value="classpath:config/quartz.properties"/>
<property name="dataSource" ref="dataSource"/>
<property name="transactionManager" ref="transactionManager"/>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment