{"id":120,"date":"2011-06-06T13:19:37","date_gmt":"2011-06-06T20:19:37","guid":{"rendered":"http:\/\/sloanseaman.com\/wordpress\/?p=120"},"modified":"2012-03-03T13:23:50","modified_gmt":"2012-03-03T20:23:50","slug":"spring-and-quartz-and-persistence","status":"publish","type":"post","link":"http:\/\/sloanseaman.com\/wordpress\/2011\/06\/06\/spring-and-quartz-and-persistence\/","title":{"rendered":"Spring, Quartz, and Persistence"},"content":{"rendered":"<p>Spring is great, Spring is fun, Spring is best when it&#8217;s one on one (props to George Michael).  <\/p>\n<p>No, really, Spring is a great architecture.  Its easily configured, highly flexible, and comes with a ton of already coded utils for doing anything from REST based integration calls to quickly deployed Servlets.<\/p>\n<p>One of the packages that you may eventually end up using it the <a href=\"http:\/\/blog.springsource.com\/2010\/01\/05\/task-scheduling-simplifications-in-spring-3-0\/\">Spring Scheduler<\/a>.  The Spring Scheduler is a Spring wrapper to the very nice scheduling library, <a href=\"http:\/\/www.quartz-scheduler.org\/\">Quartz<\/a>.  The Spring Scheduler is simple and easy until you have to worry about more than one server.  As soon as you get into a clustered environment (especially one that you back the scheduling with a database) it starts to swagger like a frat boy during homecoming.  Unless your careful it just may puke on you.<\/p>\n<p>So you don&#8217;t waste 3 (yes 3) days on getting it setup and working I thought I&#8217;d go over what it takes to get Quartz to run in a Spring clustered environment with a db backend and actual usable Spring beans (autowiring, etc).<\/p>\n<p>The first thing you will want to do it get the Quartz libraries.  Careful now because the latest Quartz libs (2.x) don&#8217;t work with Spring.  You have to use something earlier (at the time of this writing it&#8217;s at 1.8.6).<br \/>\nHere is the Maven dependencies for it:<\/p>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n\t&lt;dependency&gt;\r\n\t\t&lt;groupId&gt;org.quartz-scheduler&lt;\/groupId&gt;\r\n\t\t&lt;artifactId&gt;quartz&lt;\/artifactId&gt;\r\n\t\t&lt;version&gt;1.8.5&lt;\/version&gt;\r\n\t\t&lt;exclusions&gt;\r\n\t\t\t&lt;exclusion&gt;\r\n\t\t\t\t&lt;groupId&gt;com.mchange.c3p0&lt;\/groupId&gt;\r\n\t\t\t\t&lt;artifactId&gt;com.springsource.com.mchange.v2.c3p0&lt;\/artifactId&gt;\r\n\t\t\t&lt;\/exclusion&gt;\r\n\t\t&lt;\/exclusions&gt;\r\n\t&lt;\/dependency&gt;\r\n\r\n        &lt;dependency&gt;\r\n            &lt;groupId&gt;org.quartz-scheduler&lt;\/groupId&gt;\r\n            &lt;artifactId&gt;quartz-oracle&lt;\/artifactId&gt;\r\n            &lt;version&gt;1.8.5&lt;\/version&gt;\r\n        &lt;\/dependency&gt;\r\n<\/pre>\n<p>Please note that I excluded C3P0 because I already use it in most of my projects.  If you don&#8217;t use C3P0 then your Maven dependencies will be:<\/p>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n\t&lt;dependency&gt;\r\n\t\t&lt;groupId&gt;org.quartz-scheduler&lt;\/groupId&gt;\r\n\t\t&lt;artifactId&gt;quartz&lt;\/artifactId&gt;\r\n\t\t&lt;version&gt;1.8.5&lt;\/version&gt;\r\n\t&lt;\/dependency&gt;\r\n\r\n        &lt;dependency&gt;\r\n            &lt;groupId&gt;org.quartz-scheduler&lt;\/groupId&gt;\r\n            &lt;artifactId&gt;quartz-oracle&lt;\/artifactId&gt;\r\n            &lt;version&gt;1.8.5&lt;\/version&gt;\r\n        &lt;\/dependency&gt;\r\n<\/pre>\n<p>I&#8217;m assuming your application already has a dataSource and a transactionManager defined. Something like:<\/p>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n\t&lt;bean id=&quot;dataSource&quot; class=&quot;com.mchange.v2.c3p0.ComboPooledDataSource&quot;\r\n\t\tdestroy-method=&quot;close&quot;&gt;\r\n\t\t&lt;property name=&quot;driverClass&quot; value=&quot;${driver}&quot;&gt;&lt;\/property&gt;\r\n\t\t&lt;property name=&quot;jdbcUrl&quot; value=&quot;${url}&quot; \/&gt;\r\n\t\t&lt;property name=&quot;user&quot; value=&quot;${username}&quot; \/&gt;\r\n\t\t&lt;property name=&quot;password&quot; value=&quot;${password}&quot; \/&gt;\r\n\t\t&lt;property name=&quot;initialPoolSize&quot; value=&quot;2&quot; \/&gt;\r\n\t\t&lt;property name=&quot;minPoolSize&quot; value=&quot;2&quot; \/&gt;\r\n\t\t&lt;property name=&quot;maxPoolSize&quot; value=&quot;5&quot; \/&gt;\r\n\t\t&lt;property name=&quot;checkoutTimeout&quot; value=&quot;100&quot; \/&gt;\r\n\t\t&lt;property name=&quot;maxStatements&quot; value=&quot;500&quot; \/&gt;\r\n\t\t&lt;property name=&quot;testConnectionOnCheckin&quot; value=&quot;false&quot; \/&gt;\r\n\t\t&lt;property name=&quot;maxIdleTime&quot; value=&quot;180&quot; \/&gt;\r\n\t\t&lt;property name=&quot;idleConnectionTestPeriod&quot; value=&quot;1000&quot; \/&gt;\r\n\t&lt;\/bean&gt;\r\n\r\n\t&lt;bean id=&quot;entityManagerFactory&quot;\r\n\t\tclass=&quot;org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean&quot;&gt;\r\n\t\t&lt;property name=&quot;persistenceProvider&quot; ref=&quot;hibernatePersistence&quot; \/&gt;\r\n\t\t&lt;property name=&quot;persistenceUnitName&quot; value=&quot;model&quot; \/&gt;\r\n\t\t&lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot; \/&gt;\r\n\t\t&lt;property name=&quot;jpaVendorAdapter&quot;&gt;\r\n\t\t\t&lt;bean class=&quot;org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter&quot;&gt;\r\n\t\t\t\t&lt;property name=&quot;showSql&quot; value=&quot;true&quot; \/&gt;\r\n\t\t\t\t&lt;property name=&quot;databasePlatform&quot; value=&quot;org.hibernate.dialect.Oracle10gDialect&quot; \/&gt;\r\n\t\t\t&lt;\/bean&gt;\r\n\t\t&lt;\/property&gt;\r\n\t&lt;\/bean&gt;\r\n\r\n\t&lt;bean id=&quot;transactionManager&quot; class=&quot;org.springframework.orm.jpa.JpaTransactionManager&quot;&gt;\r\n\t\t&lt;property name=&quot;entityManagerFactory&quot; ref=&quot;entityManagerFactory&quot; \/&gt;\r\n\t\t&lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot; \/&gt;\r\n\t&lt;\/bean&gt;\r\n<\/pre>\n<p>You will need an <code>dataSource<\/code> and <code>transactionManager<\/code> for Quartz to use.   Quartz, in order to make sure that multiple servers are not firing the same event, uses the database to keep track of what has executed and what needs to be executed.  This allows multiple servers that have the application on it to not fire the same event at the same time.  Only one server will fire the job (the server that will do the job is random).<\/p>\n<p>Next we need to set up Quartz to run under Spring.  Now, I mentioned Spring Scheduler above.  It has it&#8217;s one tags, &lt;task&gt;, that work great in single server environments.  Anything other than that and you will be using regular Spring bean definitions.  There&#8217;s quite a bit to the configuration so I&#8217;ll go over each piece and then present the whole configuration as one file.<\/p>\n<p>First lets setup the scheduler that will run the jobs for us.  This uses the Spring framework <code>SchedulerFactoryBean <\/code>to create the scheduler that wraps Quartz and runs our jobs for us.<\/p>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n&lt;bean id=&quot;scheduler&quot;\r\n\tclass=&quot;org.springframework.scheduling.quartz.SchedulerFactoryBean&quot; lazy-init=&quot;false&quot;&gt;\r\n\r\n<\/pre>\n<p>Next we need to setup some basic configuration parameters to get the system working:<\/p>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n&lt;property name=&quot;autoStartup&quot; value=&quot;true&quot;\/&gt; \r\n&lt;property name=&quot;applicationContextSchedulerContextKey&quot; value=&quot;applicationContext&quot;\/&gt;\r\n&lt;property name=&quot;waitForJobsToCompleteOnShutdown&quot; value=&quot;true&quot;\/&gt;\r\n&lt;property name=&quot;overwriteExistingJobs&quot; value=&quot;true&quot;\/&gt;\r\n&lt;property name=&quot;dataSource&quot;&gt;\r\n\t&lt;ref bean=&quot;dataSource&quot;\/&gt;\r\n&lt;\/property&gt;\r\n&lt;property name=&quot;transactionManager&quot;&gt;\r\n\t&lt;ref bean=&quot;transactionManager&quot;\/&gt;\r\n&lt;\/property&gt;\r\n<\/pre>\n<ul>\n<li><code>autoStartup <\/code>&#8211; Start the Scheduler as soon as it is instantiated<\/li>\n<li><code>applicationContextSchedulerContextKey <\/code>&#8211; The key in the map that will be passed to our jobs that has the Spring Application Context in it<\/li>\n<li><code>waitForJobsToCompleteOnShutdown <\/code>&#8211; Should we not shut down if a job is in the middle of executing<\/li>\n<li><code>overwriteExistingJobs <\/code>&#8211; If the configuration in this file changes from what Quartz has in the database, should the database values be overwritten<\/li>\n<li><code>dataSource <\/code>&#8211; The dataSource (already defined as a bean somewhere) that should be used for database connectivity<\/li>\n<li><code>transactionManager <\/code>&#8211; The transactionManager (already defined as a bean somewhere) that should be used to retrieve transactions<\/li>\n<\/ul>\n<p>There are additional properties but they merit more individual attention:<\/p>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n\t\t&lt;property name=&quot;quartzProperties&quot;&gt;\r\n\t\t\t&lt;props&gt;\r\n\t\t\t\t&lt;!-- Job store --&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.jobStore.misfireThreshold&quot;&gt;6000000&lt;\/prop&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.jobStore.driverDelegateClass&quot;&gt;org.quartz.impl.jdbcjobstore.oracle.OracleDelegate&lt;\/prop&gt;\r\n\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.jobStore.isClustered&quot;&gt;true&lt;\/prop&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.jobStore.clusterCheckinInterval&quot;&gt;20000&lt;\/prop&gt;\r\n\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.scheduler.instanceName&quot;&gt;ClusteredScheduler&lt;\/prop&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.scheduler.instanceId&quot;&gt;AUTO&lt;\/prop&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.scheduler.jmx.export&quot;&gt;true&lt;\/prop&gt;\r\n\t\t\t&lt;\/props&gt;\r\n\t\t&lt;\/property&gt;\r\n<\/pre>\n<ul>\n<li><code>misfireThreshold <\/code>&#8211; The the number of milliseconds the scheduler will &#8216;tolerate&#8217; a trigger to pass its next-fire-time by, before being considered &#8220;misfired&#8221;<\/li>\n<li><code>driverDelegateClass <\/code>&#8211; The class to use to connect to your database instance.  I use Oracle. This should be changed to your database type<\/li>\n<li><code>isClustered <\/code>&#8211; Is this a clustered instance.  Yes because we are running on more than one server<\/li>\n<li><code>clusterCheckinInterval <\/code>&#8211; How often should things be checked<\/li>\n<li><code>instanceName <\/code>&#8211; The name of this cluster instance<\/li>\n<li><code>instanceId <\/code>&#8211; How will the instances be id&#8217;ed?  AUTO lets Quartz deal with it<\/li>\n<li><code>jmx.export<\/code> &#8211; Should management beans be exported?  I like MBeans so put it as true.  This is not required for persistence<\/li>\n<\/ul>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n\t\t&lt;property name=&quot;triggers&quot;&gt;\r\n\t\t\t&lt;list&gt;\r\n\t\t\t\t&lt;ref bean=&quot;caseArchiveSuspendedJobTrigger&quot;\/&gt;\r\n\t\t         &lt;\/list&gt;\r\n\t\t&lt;\/property&gt;\r\n<\/pre>\n<ul>\n<li><code>triggers <\/code>&#8211; The triggers are a list of jobs that need to be executed.  More on that in a bit<\/li>\n<\/ul>\n<p>The last property that needs defined is the  factory that will create the Jobs.  Spring Scheduler ships with a version that works by creating a new instance of a job each time the job is to be executed.  The issue with this is that the job is not in the scope of the Spring application because of this.  The Job does have its parameters set but any autowiring or, more importantly, transactions, will not exists.  This means that if you job does any database manipulation that requires a commit, it will not work.  I have created my own SpringBeanJobFactory that gets around this by creating the job as a regular Spring bean and then using that instance of the bean to execute the job.<\/p>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n\t\t&lt;property name=&quot;jobFactory&quot;&gt;\r\n\t\t\t&lt;bean class=&quot;mycode.scheduled.SpringBeanJobFactory&quot;\/&gt;\r\n\t\t&lt;\/property&gt;\r\n<\/pre>\n<p>And the code for the class:<\/p>\n<pre class=\"brush: java; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\npackage mycode.scheduled;\r\n\r\nimport org.quartz.SchedulerContext;\r\nimport org.quartz.spi.TriggerFiredBundle;\r\nimport org.springframework.beans.BeanWrapper;\r\nimport org.springframework.beans.MutablePropertyValues;\r\nimport org.springframework.beans.PropertyAccessorFactory;\r\nimport org.springframework.scheduling.quartz.SpringBeanJobFactory;\r\nimport org.springframework.web.context.support.XmlWebApplicationContext;\r\n\r\n\/**\r\n * @author sseaman - mostly a cut\/paste from the source class\r\n *\r\n *\/\r\npublic class SpringBeanJobFactory \r\n\textends org.springframework.scheduling.quartz.SpringBeanJobFactory\r\n{\r\n\r\n\tprivate String[] ignoredUnknownProperties;\r\n\r\n\tprivate SchedulerContext schedulerContext;\r\n\r\n\r\n\t@Override\r\n\tpublic void setIgnoredUnknownProperties(String[] ignoredUnknownProperties) {\r\n\t\tsuper.setIgnoredUnknownProperties(ignoredUnknownProperties);\r\n\t\tthis.ignoredUnknownProperties = ignoredUnknownProperties;\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic void setSchedulerContext(SchedulerContext schedulerContext) {\r\n\t\tsuper.setSchedulerContext(schedulerContext);\r\n\t\tthis.schedulerContext = schedulerContext;\r\n\t}\r\n\t\r\n\t\/**\r\n\t * An implementation of SpringBeanJobFactory that retrieves the bean from\r\n\t * the Spring context so that autowiring and transactions work\r\n\t * \r\n\t * This method is overriden.\r\n\t * @see org.springframework.scheduling.quartz.SpringBeanJobFactory#createJobInstance(org.quartz.spi.TriggerFiredBundle)\r\n\t *\/\r\n\t@Override\r\n\tprotected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {\r\n\t\tXmlWebApplicationContext ctx = ((XmlWebApplicationContext)schedulerContext.get(&quot;applicationContext&quot;));\r\n\t\tObject job = ctx.getBean(bundle.getJobDetail().getName());\r\n\t\tBeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);\r\n\t\tif (isEligibleForPropertyPopulation(bw.getWrappedInstance())) {\r\n\t\t\tMutablePropertyValues pvs = new MutablePropertyValues();\r\n\t\t\tif (this.schedulerContext != null) {\r\n\t\t\t\tpvs.addPropertyValues(this.schedulerContext);\r\n\t\t\t}\r\n\t\t\tpvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());\r\n\t\t\tpvs.addPropertyValues(bundle.getTrigger().getJobDataMap());\r\n\t\t\tif (this.ignoredUnknownProperties != null) {\r\n\t\t\t\tfor (String propName : this.ignoredUnknownProperties) {\r\n\t\t\t\t\tif (pvs.contains(propName) &amp;&amp; !bw.isWritableProperty(propName)) {\r\n\t\t\t\t\t\tpvs.removePropertyValue(propName);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbw.setPropertyValues(pvs);\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tbw.setPropertyValues(pvs, true);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn job;\r\n\t}\r\n\t\r\n}\r\n<\/pre>\n<p>As you can see the code gets the <code>applicationContext <\/code>and then uses that to retrieve the bean from Spring.  This bean will be in any defined transactions and will have all Autowiring already done.<\/p>\n<p>The last steps are to define the trigger and the bean that represents the job.  For this we will use more of the Spring Scheduler classes.<\/p>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n\t&lt;bean id=&quot;caseArchiveSuspendedJobTrigger&quot;\r\n\t\tclass=&quot;org.springframework.scheduling.quartz.CronTriggerBean&quot;&gt;\r\n\t\t&lt;property name=&quot;jobDetail&quot;&gt;\r\n\t\t\t&lt;bean name=&quot;caseArchiveSuspendedJob&quot; class=&quot;org.springframework.scheduling.quartz.JobDetailBean&quot;&gt;\r\n\t\t\t\t&lt;property name=&quot;name&quot; value=&quot;caseArchiveSuspendedJob&quot;\/&gt;\r\n\t\t\t\t&lt;property name=&quot;jobClass&quot; value=&quot;mycode.job.CaseArchiveSuspendedJob&quot;\/&gt;\r\n\t\t\t&lt;\/bean&gt;\r\n\t\t&lt;\/property&gt;\r\n\t\t&lt;property name=&quot;cronExpression&quot; value=&quot;0 0 3 * * ?&quot;\/&gt;\r\n\t&lt;\/bean&gt;\r\n\t\r\n\t&lt;bean id=&quot;caseArchiveSuspendedJob&quot; class=&quot;mycode.job.CaseArchiveSuspendedJob&quot;&gt;\r\n\t\t&lt;property name=&quot;daysToArchiveSuspended&quot; value=&quot;6&quot;\/&gt;\t\r\n\t&lt;\/bean&gt;\r\n<\/pre>\n<p>First, create a definition for the type of trigger. Again, mine is Cron based.  The CronTriggerBean requires two properties:<\/p>\n<ul>\n<li><code>jobDetail<\/code><\/li>\n<li><code>cronExpression<\/code><\/li>\n<\/ul>\n<p>The <code>cronExpression <\/code>is straightforward.  The <code>jobDetail <\/code>requires some explanation.<\/p>\n<p>The <code>jobDetail <\/code>uses an instance of the JobDetailBean but not in the way Spring Scheduler intended.  The <code>name<\/code> is set to correspond to the bean <code>id<\/code> of the bean that represents the actual job.  The <code>jobClass<\/code> is the class of the bean (same as defined in the bean <code>class<\/code> attribute).  This allows the SpringBeanJobFactory to get the actual Spring instance of the bean by using the <code>name<\/code> while still satisfying the requirements of the JobDetailBean (<code>jobClass <\/code>is required).<\/p>\n<p>Lastly, we just define our job bean as we would any other bean.<\/p>\n<p>Here is a skeleton version of my job:<\/p>\n<pre class=\"brush: java; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\npublic class CaseArchiveSuspendedJob {\r\n    @Override\r\n\t@Transactional(propagation = Propagation.REQUIRED, readOnly = false)\r\n\tpublic void execute(JobExecutionContext context)\r\n\t\tthrows JobExecutionException\r\n\t{\r\n            \/\/ do something with the db in a transaction\r\n     }\r\n}\r\n<\/pre>\n<p>And now, here is everything in one file:<\/p>\n<pre class=\"brush: xml; light: false; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;\r\n&lt;beans xmlns=&quot;http:\/\/www.springframework.org\/schema\/beans&quot;\r\n\txmlns:xsi=&quot;http:\/\/www.w3.org\/2001\/XMLSchema-instance&quot;\r\n\tdefault-autowire=&quot;byName&quot;\r\n\txsi:schemaLocation=&quot;http:\/\/www.springframework.org\/schema\/beans http:\/\/www.springframework.org\/schema\/beans\/spring-beans-3.0.xsd&quot;&gt;\r\n\t\r\n\t&lt;bean id=&quot;scheduler&quot;\r\n\t\tclass=&quot;org.springframework.scheduling.quartz.SchedulerFactoryBean&quot; lazy-init=&quot;false&quot;&gt;\r\n\t\t&lt;property name=&quot;autoStartup&quot; value=&quot;true&quot;\/&gt;\r\n\t\t&lt;property name=&quot;applicationContextSchedulerContextKey&quot; value=&quot;applicationContext&quot;\/&gt;\r\n\t\t&lt;property name=&quot;waitForJobsToCompleteOnShutdown&quot; value=&quot;true&quot;\/&gt;\r\n\t\t&lt;property name=&quot;overwriteExistingJobs&quot; value=&quot;true&quot;\/&gt;\r\n\t\t&lt;property name=&quot;dataSource&quot;&gt;\r\n\t\t\t&lt;ref bean=&quot;dataSource&quot;\/&gt;\r\n\t\t&lt;\/property&gt;\r\n\t\t&lt;property name=&quot;transactionManager&quot;&gt;\r\n\t\t\t&lt;ref bean=&quot;transactionManager&quot;\/&gt;\r\n\t\t&lt;\/property&gt;\r\n\t\t&lt;property name=&quot;jobFactory&quot;&gt;\r\n\t\t\t&lt;bean class=&quot;mycode.scheduled.SpringBeanJobFactory&quot;\/&gt;\r\n\t\t&lt;\/property&gt;\r\n\t\t&lt;property name=&quot;quartzProperties&quot;&gt;\r\n\t\t\t&lt;props&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.jobStore.misfireThreshold&quot;&gt;6000000&lt;\/prop&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.jobStore.driverDelegateClass&quot;&gt;org.quartz.impl.jdbcjobstore.oracle.OracleDelegate&lt;\/prop&gt;\r\n\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.jobStore.isClustered&quot;&gt;true&lt;\/prop&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.jobStore.clusterCheckinInterval&quot;&gt;20000&lt;\/prop&gt;\r\n\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.scheduler.instanceName&quot;&gt;SgsClusteredScheduler&lt;\/prop&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.scheduler.instanceId&quot;&gt;AUTO&lt;\/prop&gt;\r\n\t\t\t\t&lt;prop key=&quot;org.quartz.scheduler.jmx.export&quot;&gt;true&lt;\/prop&gt;\r\n\t\t\t&lt;\/props&gt;\r\n\t\t&lt;\/property&gt;\r\n\t\t&lt;property name=&quot;triggers&quot;&gt;\r\n\t\t\t&lt;list&gt;\r\n\t\t\t\t&lt;ref bean=&quot;caseArchiveSuspendedJobTrigger&quot;\/&gt;\r\n\t\t\t&lt;\/list&gt;\r\n\t\t&lt;\/property&gt;\r\n\t&lt;\/bean&gt;\r\n\t\r\n\t&lt;bean id=&quot;caseArchiveSuspendedJobTrigger&quot;\r\n\t\tclass=&quot;org.springframework.scheduling.quartz.CronTriggerBean&quot;&gt;\r\n\t\t&lt;property name=&quot;jobDetail&quot;&gt;\r\n\t\t\t&lt;bean name=&quot;caseArchiveSuspendedJob&quot; class=&quot;org.springframework.scheduling.quartz.JobDetailBean&quot;&gt;\r\n\t\t\t\t&lt;property name=&quot;name&quot; value=&quot;caseArchiveSuspendedJob&quot;\/&gt;\r\n\t\t\t\t&lt;property name=&quot;jobClass&quot; value=&quot;mycode.job.CaseArchiveSuspendedJob&quot;\/&gt;\r\n\t\t\t&lt;\/bean&gt;\r\n\t\t&lt;\/property&gt;\r\n\t\t&lt;property name=&quot;cronExpression&quot; value=&quot;* * * * * * ?&quot;\/&gt;\r\n\t&lt;\/bean&gt;\r\n\t\r\n\t&lt;bean id=&quot;caseArchiveSuspendedJob&quot; class=&quot;mycode.job.CaseArchiveSuspendedJob&quot;&gt;\r\n\t\t&lt;property name=&quot;daysToArchiveSuspended&quot; value=&quot;10&quot;\/&gt;\t\r\n\t&lt;\/bean&gt;\r\n&lt;\/beans&gt;\r\n<\/pre>\n<p>As you can see, there is quite a lot setting up Spring and Quartz to easily work within a transaction.  I spent way too much time researching this.  I hope this helps someone&#8230;.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Spring is great, Spring is fun, Spring is best when it&#8217;s one on one (props to George Michael). No, really, Spring is a great architecture. Its easily configured, highly flexible, and comes with a ton of already coded utils for &hellip; <a href=\"http:\/\/sloanseaman.com\/wordpress\/2011\/06\/06\/spring-and-quartz-and-persistence\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[7,10],"tags":[29,16,15],"_links":{"self":[{"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/posts\/120"}],"collection":[{"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/comments?post=120"}],"version-history":[{"count":19,"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/posts\/120\/revisions"}],"predecessor-version":[{"id":138,"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/posts\/120\/revisions\/138"}],"wp:attachment":[{"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/media?parent=120"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/categories?post=120"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/sloanseaman.com\/wordpress\/wp-json\/wp\/v2\/tags?post=120"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}