Persistance des tâches Quartz avec Spring Boot et MySQL

La mise en œuvre de la persistance dans Quartz Scheduler est essentielle pour garantir que les tâches planifiées survivetn aux redémarrages de l'application et pour permettre une exécution en cluster. En utilisant une base de données comme support de stockage (JobStoreTX), Quartz peut gérer l'état des déclencheurs et des jobs de manière centralisée.

1. Dépendances Maven

Pour intégrer Quartz avec un support de persistance JDBC, vous devez inclure le starter Spring Boot correspondant ainsi que le pilote de votre base de données.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.0</version>
    </dependency>
</dependencies>

2. Configuration du Scheduler

La classe de configuration suivante initialise le SchedulerFactoryBean en le liant à la source de données de l'application.

package com.app.config;

import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;

@Configuration
public class AppSchedulerConfig {

    @Bean("customSchedulerFactory")
    public SchedulerFactoryBean customSchedulerFactory(@Qualifier("quartzSettings") Properties quartzProperties, DataSource dbSource) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setOverwriteExistingJobs(true);
        factory.setDataSource(dbSource);
        factory.setQuartzProperties(quartzProperties);
        factory.setJobFactory(new SpringBeanJobFactory());
        return factory;
    }

    @Bean("quartzSettings")
    public Properties quartzSettings() throws IOException {
        PropertiesFactoryBean propertiesBean = new PropertiesFactoryBean();
        propertiesBean.setLocation(new ClassPathResource("quartz.properties"));
        propertiesBean.afterPropertiesSet();
        return propertiesBean.getObject();
    }

    @Bean(name = "mainScheduler")
    public Scheduler mainScheduler(@Qualifier("customSchedulerFactory") SchedulerFactoryBean factory) {
        return factory.getScheduler();
    }
}

3. Définition de la tâche (Job)

Le Job contient la logique métier à exécuter. Ici, nous récupérons des paramètres dynamiques via la JobDataMap.

package com.app.jobs;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DataProcessingJob implements Job {
    private static final Logger log = LoggerFactory.getLogger(DataProcessingJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("Exécution de la tâche planifiée...");
        JobDataMap data = context.getMergedJobDataMap();
        
        data.keySet().forEach(key -> {
            Object value = data.get(key);
            log.info("Paramètre - {} : {}", key, value);
        });
    }
}

4. Contrôleur de gestion des tâches

Ce point d'entrée API permet de programmer une nouvelle tâche en fournissant une expression Cron.

package com.app.controller;

import com.app.jobs.DataProcessingJob;
import org.quartz.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/api/scheduler")
public class JobController {

    @Resource(name = "mainScheduler")
    private Scheduler scheduler;

    @PostMapping("/schedule")
    public String scheduleJob(@RequestParam String cronExpression) throws SchedulerException {
        JobDetail job = JobBuilder.newJob(DataProcessingJob.class)
                .withIdentity("job_worker", "group_1")
                .requestRecovery(true)
                .build();

        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger_worker", "group_1")
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                .startNow()
                .build();

        job.getJobDataMap().put("status", "initialized");
        scheduler.scheduleJob(job, trigger);
        
        return "Tâche programmée avec succès.";
    }
}

5. Configuration des propriétés

Le fichier application.yml définit la connexion à la base de données, tandis que quartz.properties configure le comportement spécifique au moteur de tâches.

application.yml :``` spring: datasource: url: jdbc:mysql://localhost:3306/quartz_db?useSSL=false&serverTimezone=UTC username: root password: password quartz: job-store-type: jdbc jdbc: initialize-schema: never


**quartz.properteis :**```
org.quartz.scheduler.instanceName = ClusteredScheduler
org.quartz.scheduler.instanceId = AUTO

org.quartz.jobStore.class = org.springframework.scheduling.quartz.LocalDataSourceJobStore
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 15000

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 5

6. Schéma de base de données (SQL)

Quartz nécessite un ensemble de tables pour stocker les définitions de jobs et les états des déclencheurs. Voici les principales structures nécessaires (version simplifiée pour MySQL).

CREATE TABLE QRTZ_JOB_DETAILS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    JOB_CLASS_NAME VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
);

CREATE TABLE QRTZ_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME, JOB_NAME, JOB_GROUP)
);

CREATE TABLE QRTZ_CRON_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    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 (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);

CREATE TABLE QRTZ_LOCKS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME, LOCK_NAME)
);

Étiquettes: Quartz-Scheduler spring-boot MySQL Java JDBC-Persistence

Publié le 2 novembre à 03h11