viernes, 3 de octubre de 2014

Como usar Quartz 1.8.x en modo Cluster (Hibernate - MySql) con Spring 4


QUARTZ SCHEDULING, SPRING, HIBERNATE AND CLUSTERING

Hola a todos, agregar Quartz a un proyecto java con Spring es muy fácil... pero integrarlo con la base de datos para sincronizar el temporizador entre instancias de cluster de nuestra aplicación no lo es tanto.
Sepan que Quartz Scheduler es uno de los programadores de tareas mas importantes del mundo Java. En mi caso trabajé bastante con esta librería en mis proyectos, pero tuve algunas complicaciones a la hora de implementar una solución que tenga en cuenta la posibilidad de trabajar en paralelo en cuanto a servidores en donde se encuentre corriendo mi app.
Ahora voy a intentar detallarles mas o menos con un ejemplo como hacerlo ... ya que por estos días me encontré con la necesidad de programar tareas en mi aplicación web, pero como tengo un ambiente de varias instancias en cluster, no quería que corran en todas estas instancias, sino que la sincronización las incluya a todas.
Tengan en cuenta que Spring Framework es compatible con la versión 1.x de Quartz, para usar Quartz 2.x hay que usarlo standalone.

Ambiente:


  • POM Dependencias
<dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context-support</artifactId>
             <version>4.0.6.RELEASE</version>
        </dependency>
<dependency>
             <groupId>org.quartz-scheduler</groupId>
             <artifactId>quartz</artifactId>
             <version>1.8.6</version>
             <exclusions>
<exclusion>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
</exclusion>
     </exclusions> 
        </dependency>
<dependency>
             <groupId>org.quartz-scheduler</groupId>
             <artifactId>quartz-jobs</artifactId>
             <version>1.8.6</version>
        </dependency>


  • quartz.xml (Spring)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

 <bean
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="location">
   <value>classpath:environment.properties</value>
  </property>
 </bean>

 <!-- Runnable Job: this will be used to call my actual job. Must implement 
  runnable -->
 <bean name="exampleSimpleJob" class="example.ExampleSimpleJob"></bean>

 <!-- New Clusterable Definition -->
 <bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
  <property name="jobClass" value="example.GenericQuartzJob" />
  <property name="jobDataAsMap">
   <map>
    <entry key="batchProcessorName" value="exampleSimpleJob" />
   </map>
  </property>
 </bean>

 <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
  <!-- see the example of method invoking job above -->
  <property name="jobDetail" ref="jobDetail" />
  <!-- 10 seconds -->
  <property name="startDelay" value="10000" />
  <!-- repeat every 50 seconds -->
  <property name="repeatInterval" value="50000" />
 </bean>

 <!-- This will execute SQL scripts to recreate the quartz tables at appserver 
  boot time. -->
 <bean id="quartzDbInitializer"
  class="org.springframework.jdbc.datasource.init.DataSourceInitializer">
  <property name="dataSource" ref="dataSource" />
  <property name="enabled" value="true" />
  <property name="databasePopulator">
   <bean
    class="org.springframework.jdbc.datasource.init.ResourceDatabasePopulator">
    <property name="continueOnError" value="true" />
    <property name="ignoreFailedDrops" value="true" />
    <property name="sqlScriptEncoding" value="UTF-8" />
    <property name="scripts">
     <array>
      <value type="org.springframework.core.io.Resource">
       classpath:tables_mysql.sql
      </value>
     </array>
    </property>
   </bean>
  </property>
 </bean>


 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="triggers">
   <list>
    <ref bean="simpleTrigger" />
   </list>
  </property>
  <!-- Add this to make the Spring Context Available -->
  <property name="applicationContextSchedulerContextKey">
   <value>applicationContext</value>
  </property>
  <property name="quartzProperties">
   <props>
    <prop key="org.quartz.scheduler.instanceName">InstanceNameX</prop>
    <prop key="org.quartz.scheduler.instanceId">AUTO</prop>

    <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
    <prop key="org.quartz.threadPool.threadCount">3</prop>
    <prop key="org.quartz.threadPool.threadPriority">5</prop>

    <prop key="org.quartz.jobStore.misfireThreshold">60000</prop>

    <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
   <prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.MSSQLDelegate</prop>
    <prop key="org.quartz.jobStore.useProperties">false</prop>
    <prop key="org.quartz.jobStore.dataSource">myDS</prop>
    <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>

    <prop key="org.quartz.jobStore.isClustered">true</prop>
    <prop key="org.quartz.jobStore.clusterCheckinInterval">20000</prop>
    <!-- db config -->
    <prop key="org.quartz.dataSource.myDS.driver">com.mysql.jdbc.Driver</prop>
    <prop key="org.quartz.dataSource.myDS.URL">jdbc:mysql://localhost:3306/example_db</prop>
    <prop key="org.quartz.dataSource.myDS.user">${jdbc.username}</prop>
    <prop key="org.quartz.dataSource.myDS.password">${jdbc.password}</prop>
    <prop key="org.quartz.dataSource.myDS.maxConnections">5</prop>
    <prop key="org.quartz.dataSource.myDS.validationQuery">select 1</prop>
    <!-- Query que ejecuta QUARTZ por default -->
    <!-- "SELECT * FROM {0}LOCKS WHERE SCHED_NAME = {1} AND LOCK_NAME = ? FOR UPDATE" -->
   </props>
  </property>
 </bean>

 <bean id="hibernateInterceptor" class="org.springframework.orm.hibernate3.HibernateInterceptor">
  <property name="sessionFactory">
   <ref bean="sessionFactory" />
  </property>
 </bean>

</beans>

  • Contenido tables_mysql.sql
Este archivo contiene la creación de las tablas que utiliza Quartz para mantener su contexto en Mysql:

-- Mysql Quartz Tables ...
DROP TABLE IF EXISTS QRTZ_JOB_LISTENERS; DROP TABLE IF EXISTS QRTZ_TRIGGER_LISTENERS; DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS ( JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_VOLATILE VARCHAR(1) NOT NULL, IS_STATEFUL VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_JOB_LISTENERS ( JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, JOB_LISTENER VARCHAR(200) NOT NULL, PRIMARY KEY (JOB_NAME,JOB_GROUP,JOB_LISTENER), FOREIGN KEY (JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, IS_VOLATILE VARCHAR(1) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT(13) NULL, PREV_FIRE_TIME BIGINT(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT(13) NOT NULL, END_TIME BIGINT(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT(2) NULL, JOB_DATA BLOB NULL, PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT(7) NOT NULL, REPEAT_INTERVAL BIGINT(12) NOT NULL, TIMES_TRIGGERED BIGINT(10) NOT NULL, PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(200) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_TRIGGER_LISTENERS ( TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, TRIGGER_LISTENER VARCHAR(200) NOT NULL, PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_LISTENER), FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, IS_VOLATILE VARCHAR(1) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_STATEFUL VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(13) NOT NULL, PRIMARY KEY (INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (LOCK_NAME) ); INSERT INTO QRTZ_LOCKS values('TRIGGER_ACCESS'); INSERT INTO QRTZ_LOCKS values('JOB_ACCESS'); INSERT INTO QRTZ_LOCKS values('CALENDAR_ACCESS'); INSERT INTO QRTZ_LOCKS values('STATE_ACCESS'); INSERT INTO QRTZ_LOCKS values('MISFIRE_ACCESS'); commit;

  • GenericQuartzJob (java)


package example;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerContext;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class GenericQuartzJob extends QuartzJobBean {
 
    private String batchProcessorName;
    
    public String getBatchProcessorName() {
        return batchProcessorName;
    }
 
    public void setBatchProcessorName(String name) {
        this.batchProcessorName = name;
    }
 
    protected void executeInternal(JobExecutionContext jobCtx) throws JobExecutionException {
        try {
            SchedulerContext schedCtx = jobCtx.getScheduler().getContext();
            ApplicationContext appCtx = (ApplicationContext) schedCtx.get("applicationContext");
            java.lang.Runnable proc = (java.lang.Runnable) appCtx.getBean(batchProcessorName);
            proc.run();
        } catch (Exception ex) {
            throw new JobExecutionException("Unable to execute batch job: " + batchProcessorName, ex);
        }
    }
}


  • ExampleSimpleJob (Java)
package example;

public class ClientFormJob implements Runnable {

 @Override
 public void run() {
  System.out.println("EJECUTANDO JOB QUARTZ");
 }

}











No hay comentarios.:

Publicar un comentario