Using Spring and Quartz with JobStore properties

This article describes how to use the Spring Framework and Quartz together when using org.quartz.jobStore.useProperties=true, meaning that all Job data is stored in the database as properties instead of serialized Java objects.

Normally this is not possible, because the Spring class SimpleTriggerFactoryBean stores a reference to the JobDetail in the JobDataMap, which cannot be represented as a set of properties.

Frameworks and versions

We’ve used the following frameworks and versions. Maybe future versions fix the issue, making our work-around obsolete.

Framework Version Description
Spring Framework 3.1.2.RELEASE DI framework
Quartz 2.1.7 Scheduling framework

See also http://www.quartz-scheduler.org/documentation/quartz-2.1.x/configuration/ConfigJobStoreTX for more details about the Quartz JobStore configuration.

Why do we want to use useProperties=true?

The main reason to use useProperties=true is to prevent storing serialized Java objects in the database, when we need to store additional information with Quartz jobs.

First of all this makes the entries easier to read in a non-Java context, e.g. when examining the database contents with a DB-tool, or when dealing with SQL scripts.

Second, this prevents us from having to deal with changing Java classes, leading to horrifying serialization exceptions (imaging having multiple incompatible class versions serialized in a database).

Of course if serialization is properly setup, and is backwards compatible with older class versions, the problem might not be so significant, but we think using properties whenever possible reduces the risk for such errors.

Sample configuration

The following sample Spring configuration shows a typical Quartz setup that we want to use (but without our work-around). Please note that only settings relevant for this article are shown.

<bean id="SampleScheduler">
  <property name="quartzProperties">
    <props>
      <prop key="org.quartz.jobStore.useProperties">true</prop>
    </props>
  </property>
  <property name="triggers">
    <list>
      <ref bean="SampleJob.trigger"/>
    </list>
  </property>
  <property name="jobDetails">
    <list>
      <ref bean="SampleJob"/>
    </list>
  </property>
</bean>
<bean id="SampleJob">
  <property name="jobClass" value="com.trimplement.sample.SampleJob" />
  <property name="durability" value="true" />
</bean>
<bean id="SampleJob.trigger">
  <property name="jobDetail" ref="SampleJob" />
  <property name="repeatInterval" value="120000" />
</bean>

If we were to start our application using this configuration with the default beans, we would get the following exception:

java.io.IOException: JobDataMap values must be Strings when the 'useProperties' property is set.  Key of offending value: jobDetail
        at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.convertToProperty(StdJDBCDelegate.java:3113)
        at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.serializeProperties(StdJDBCDelegate.java:3080)
        at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.serializeJobData(StdJDBCDelegate.java:3032)
        at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.insertTrigger(StdJDBCDelegate.java:1052)
        at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeTrigger(JobStoreSupport.java:1209)

This exception is caused by the fact that SimpleTriggerFactoryBean stores a reference to the JobDetail in the JobDataMap. The JobDetail cannot be represented as a property, hence it is refused by Quartz.

Solution

Our work-around is to remove the JobDetail reference from the JobDataMap before returning the created SimpleTrigger. For this we subclass the SimpleTriggerFactoryBean and remove it, before returning the Trigger.

public class CustomSimpleTriggerFactoryBean extends SimpleTriggerFactoryBean {

  @Override
  public void afterPropertiesSet() throws ParseException {
    super.afterPropertiesSet();
    // Remove the JobDetail element
    getJobDataMap().remove(JobDetailAwareTrigger.JOB_DETAIL_KEY);
  }
}

Of course we need to reference our class in the Spring configuration.

<bean id="SampleJob.trigger" class="com.trimplement.sample.CustomSimpleTriggerFactoryBean">
  <property name="jobDetail" ref="SampleJob" />
  <property name="repeatInterval" value="120000" />
</bean>

When we start our application, the Job and Trigger are successfully created and persisted in the database.

Conclusion

We presented a work-around for using Spring and Quartz together when we want to store Job data as properties in the database.

This solution is fine as long as all Job data can be stored as properties instead of serialized Java objects. In general storing serialized Java objects in a database should be avoided, for obvious reasons: it requires a Java context to read/update/write such objects, and changing Java classes might break deserialization of existing persisted objects.

Instead a proper ORM-strategy such as JPA should be used to store non-trivial data structures; triggers and jobs may store IDs to such objects as simple properties.