jueves, 9 de marzo de 2017

Quartz Scheduler - Persistencia en Base de Datos Oracle y Opciones de Cluster


En el presente articulo se realizará una pequeña introducción a Quartz Scheduler para entender su funcionamiento, y luego se abordarán temas tales como la persistencia en Base de Datos Oracle como asi las respectivas configuraciones para un ambiente Clusterizado.

Para entrar en tema, una posible descripción de lo que es Quartz puede extraerse desde una rapida busqueda en Google: "Quartz Scheduler es un framework de scheduling open source que provee funcionalidad avanzada para la calendarización de tareas en Java. Entre estas están: Cualquier tarea escrita en Java que sea susceptible de ser agendada dentro del framework para ser ejecutada".




Introducción a Quartz

 Como bien se detalló anteriormente, es un framework que permite ejecutar tareas (o trabajos, a.k.a. Jobs) cada una cierta frecuencia (Scheduler), es decir, la capacidad para calendarizar un proceso X.

Un Job (o trabajo, proceso, tarea, etc.) se le denomina al objeto que almacena el codigo que se ejecutará cada cierto periodo de tiempo. Para entenderlo mejor, sería el "main" del proceso a ejecutar.

Por otro lado, un Scheduler es aquel objeto que almacenará la frecuencia con la que se ejecutará el Job. En este momento, el scheduler puede configurarse de 2 maneras: de manera Simple (cada X segundo, minutos, horas, etc.) o bien con un Cron (puede usarse una expresión cron).

Finalmente, se usa un disparador (Trigger) quien será el encargado de "disparar" el Job deseado.


Tipos de Persistencia 

Existen dos maneras de persistir la metadata de Quartz... o bien en RAM o bien en Base de Datos.

Persistencia en RAM: únicamente tener en cuenta que una vez finalizado el proceso la metadatada generada en RAM se perderá.

Persistencia en Base de Datos: en esta forma de trabajar, el Quartz guardará toda la metada en Base de Datos. Hay scripts para crear las tablas en Base de Datos Derby, MySQL, Oracle, Postgres, SQL Server, etc.


Ejemplo Práctico

Para este ejemplo es necesario contar con un JDK (1.7 o superior) y el IDE NetBeans para Java EE.

Se creara un WAR deployable en WebLogic y hará persistencia en Base de Datos. Para ello, antes de proceder con el ejemplo, descargar Quartz desde su pagina web oficial: http://www.quartz-scheduler.org/ y crear las tablas en base de datos con el script correspondiente que se encuentran en quartz-2.X.X\docs\dbTables.


1_ Crear una aplicacion "Maven: Web Application".



Luego, escribir nombre de la aplicación, version y Group ID.



Y seleccionar como servidor Oracle WebLogic Server. En caso de no contar con esta opción, agregarla haciendo clic en Add (Añadir) y siguiendo los pasos requeridos por el asistente.



2_ Añadir las dependencias de Quartz en el archivo pom.xml dentro del elemento <dependencies> usando el siguiente código:

    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz-jobs</artifactId>
        <version>2.2.3</version>
    </dependency>


Y compilar usando la opción "Clean and Build" de NetBeans.


3_  Crear una clase Java llamada TestJob.java y escribir el siguiente código:

public class TestJob implements Job {
   
    public void execute(JobExecutionContext context) {
       
        System.out.println("Hola Quartz!");
       
    }
}

Como se puede apreciar, la clase implementa de Job y sobreescribe el metodo execute. Pues aqui irá todo el codigo que se quiera ejecutar cada X cantidad de tiempo.

En este caso, se hara el tipico "Hola Mundo!", o mejor dicho... "Hola Quartz!"


4_  Crear una nueva clase Java llamada TestQuartz.java y escribir el siguiente código:

import org.quartz.JobDetail;
import org.quartz.JobBuilder;
import org.quartz.Trigger;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Scheduler;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 *
 * @author SOAJP
 */
public class TestQuartz {
   
    public static void main(String[] args) throws SchedulerException {
         
        /**
         * SCHEDULER
         */
        Scheduler scheduler = new StdSchedulerFactory().getScheduler();
       
        /**
         * JOB
         */
        JobKey jobKey = new JobKey("TestJob", "grupo1");
       
        JobDetail job = scheduler.getJobDetail(jobKey);
       
        if(job == null) {
            job = JobBuilder.newJob(TestJob.class)
                  .withIdentity(jobKey)
                  .requestRecovery(true)
                  .storeDurably(true)
                  .build();
            scheduler.addJob(job, true);
        } else {
            System.out.println("Job: " + jobKey.getGroup() + "." + jobKey.getName() + " already exist!");
        }
       
        /**
         * TRIGGER
         */
        TriggerKey triggerKey = new TriggerKey("TriggerByMinutes", "grupo1");
       
        Trigger trigger = scheduler.getTrigger(triggerKey);
       
        if(trigger == null) {
            trigger = newTrigger()
                    .withIdentity(triggerKey)
                    .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule()
                            .withIntervalInMinutes(2).repeatForever())
                    .forJob(job)
                    .build();
            scheduler.scheduleJob(trigger);
        }else {
            System.out.println("Trigger: " + triggerKey.getGroup() + "." + triggerKey.getName() + " already exist!");
        }
       
       
        // run it!
        if((job != null) && (trigger != null)) {
            scheduler.start();
        } else {
            scheduler.scheduleJob(job, trigger);
            scheduler.start();
        }
       
    }
}

Explicando el código:

Primeramente se crea un nuevo Scheduler ya que es el objeto que iniciará la ejecución y que contiene a los otros dos objetos (Job y Trigger, ya que son necesarios para la ejecución del Scheduler)).

Luego se crea una jobKey para darle un nombre al Job. Esta Key se compone del nombre del grupo al que pertenece el Job y el nombre del Job propiapmente dicho.

A continuacion se crea un Job y se intenta obtener desde las tablas la instancia de un Job con el nombre anteriormente definido.

Si el Job existe, solamente se notifica que existe y en caso contrario se procede a crear el Job con el nombre especificado en la jobKey y a añadirlo al Scheduler.

Lo mismo pasa para el Trigger, se crea una triggerKey para darle un nombre al Trigger. Esta Key
se compone del nombre del grupo al que pertenece el Trigger y el nombre del Trigger propiapmente dicho.

Si el Trigger ya existe en el scheduler obtenido desde las tablas en base de datos, se notifica que ya existe y continua la ejecución. Caso contrario se procede a crear un Trigger con el nombre definido en la triggerKey y a añadirlo al Scheduler. 

Si se presta atención, en medio de la creación del Trigger, el metodo .withIntervalInMinutes(2) le dice a Quartz que se ejecute cada 2 minutos. También exiten otros metodos para hacer que Quartz ejecute el Job cada X segundos y cada X horas (withIntervalInSeconds(35) - withIntervalInHours(1)).

Por ultimo, si tanto el Job y el Trigger existen se ejecutará el Scheduler.


5_  Crear archivo quartz.properties en el directorio: src\main\resources con el siguiente contenido:

#============================================================================
# Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName = TestQuartz
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.skipUpdateCheck = true
org.quartz.scheduler.interruptJobsOnShutdownWithWait = true

#============================================================================
# ThreadPool
#============================================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 1

#============================================================================
# JobStore
#============================================================================
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 5000

#============================================================================
# DataSource
#============================================================================
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
# --- org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource = appDS
org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.dataSource.appDS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.appDS.URL = jdbc:oracle:thin:@localhost:1521:xe
org.quartz.dataSource.appDS.user = TESTQUARTZUSER
org.quartz.dataSource.appDS.password = TESTQUARTZPASS
org.quartz.dataSource.appDS.maxConnections = 5
org.quartz.dataSource.appDS.validationQuery = select 0 from dual

Acá yace la magia de Quartz para servidores Clusterizados. Pues las propiedades que se especifican en este archivo definen su comportamiento.

Para entender las propiedades que pertenecen a la Clusterización:

org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 5000

Estas tres propiedades permiten que el Quartz se ejecute correctamente en un ambiente clusterizado.

Por ultimo, al final de dicho archivo están descritas las propiedades que pertenecen a la conexión que se efectúa con la Base de Datos para almacenar las instancias, metadata y demas del Quartz. Acá debera modificarse segun los parametros de la base de datos.


6_ Tambien será necesario agregar la dependencia al driver de Oracle JDBC (ojdbc7.jar), ya que como Quartz guardará en este ejemplo la metadata en una base de datos Oracle, es necesario contar con dicha libreria para que reconozca el driver. Para ello puede seguirse la siguiente guía: Añadir Librerias a Repositorio local en Maven.


7_ Finalmente es hora de probar... haciendo clic derecho sobre la clase TestQuartz.java y seleccionando "Run File" puede apreciarse el primer ejemplo de Quartz en acción:



Si acceden a las tablas en base de datos, verán como la metadata e instancias de Jobs, etc. Se van guardando para luego poder reanudar lo que deseemos. Por ejemplo:



 Cualquier recomendación, opinion o consulta es bienvenida siempre y cuando se traten desde el respeto y la cordialidad.

1 comentario:

  1. Hola. Es necesario guardar e una BD? y si solo quiero ejecutar una clase cada cierto tiempo? Gracias

    ResponderEliminar