Intégration de Spring Boot, Quartz et MySQL avec une Interface de Gestion

Ce guide détaille l'intégration de Spring Boot avec le planificateur de tâches Quartz et une base de données MySQL, en fournissant une interface web pour la gestion des tâches.

Configuration de Spring Boot et Quartz

Commencez par créer un projet Spring Boot et ajoutez les dépendances nécessaires dans votre fichier pom.xml.

Dépendances Maven


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.qz</groupId>
    <artifactId>quartz02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>quartz02</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <mysql.version>5.1.44</mysql.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <dependencies>
                    <!-- Utilisation d'une version compatible du pilote MySQL pour mybatis-generator -->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>${mysql.version}</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <!-- Inclure les fichiers XML des mappers pour mybatis-generator -->
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <!-- Inclure les fichiers de propriétés et YAML -->
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.properties</include>
                    <include>*.xml</include>
                    <include>*.yml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

Configuration du Pool de Connexions Druid

Pour utilisre Druid comme pool de connexions avec Quartz, créez une classe DruidConnectionProvider qui implémente org.quartz.utils.ConnectionProvider.

DruidConnectionProvider.java


package com.qz.quartz02.utils;

import com.alibaba.druid.pool.DruidDataSource;
import org.quartz.SchedulerException;
import org.quartz.utils.ConnectionProvider;

import java.sql.Connection;
import java.sql.SQLException;

/*
Configuration JDBC pour Quartz :
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties: false
org.quartz.jobStore.dataSource: qzDS
org.quartz.dataSource.qzDS.connectionProvider.class: com.zking.q03.quartz.DruidConnectionProvider
org.quartz.dataSource.qzDS.driver: com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8
org.quartz.dataSource.qzDS.user: root
org.quartz.dataSource.qzDS.password: root
org.quartz.dataSource.qzDS.maxConnections: 30
org.quartz.dataSource.qzDS.validationQuery: select 0
*/

/**
 * Extension pour le pool de connexions Druid avec Quartz.
 */
public class DruidConnectionProvider implements ConnectionProvider {

    /* Configuration du pool de connexions, mappée aux propriétés de quartz.properties */
    public String driver;
    public String URL;
    public String user;
    public String password;
    public int maxConnection;
    public String validationQuery;

    private boolean validateOnCheckout;
    private int idleConnectionValidationSeconds;
    public String maxCachedStatementsPerConnection;

    public static final int DEFAULT_DB_MAX_CONNECTIONS = 10;
    public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;

    private DruidDataSource datasource;

    @Override
    public Connection getConnection() throws SQLException {
        if (datasource == null) {
            throw new SQLException("Le pool de connexions n'est pas initialisé.");
        }
        return datasource.getConnection();
    }

    @Override
    public void shutdown() throws SQLException {
        if (datasource != null) {
            datasource.close();
        }
    }

    public void initialize() throws SQLException {
        if (this.URL == null) {
            throw new SQLException("Impossible de créer le pool de connexions : l'URL de la base de données ne peut pas être nulle.");
        }
        if (this.driver == null) {
            throw new SQLException("Impossible de créer le pilote du pool : le nom de la classe du pilote ne peut pas être nul !");
        }
        if (this.maxConnection < 0) {
            throw new SQLException("Impossible de créer les connexions maximales du pool : le nombre maximum de connexions doit être supérieur à zéro !");
        }

        datasource = new DruidDataSource();
        try {
            datasource.setDriverClassName(this.driver);
        } catch (Exception e) {
            throw new SchedulerException("Problème lors de la définition du nom de la classe du pilote du pool : " + e.getMessage(), e);
        }

        datasource.setUrl(this.URL);
        datasource.setUsername(this.user);
        datasource.setPassword(this.password);
        datasource.setMaxActive(this.maxConnection);
        datasource.setMinIdle(1);
        datasource.setMaxWait(0); // Temps d'attente maximal en ms pour obtenir une connexion
        datasource.setPoolPreparedStatements(true); // Activer les PreparedStatements en cache
        datasource.setMaxPoolPreparedStatementPerConnectionSize(DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION);

        if (this.validationQuery != null) {
            datasource.setValidationQuery(this.validationQuery);
            // Configurer la validation selon le paramètre
            if(this.validateOnCheckout) {
                datasource.setTestOnBorrow(true);
            } else {
                datasource.setTestOnReturn(true);
            }
            datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
        }
        // Démarrage du pool (peut être géré par Spring aussi)
        try {
            datasource.init();
        } catch (SQLException e) {
            throw new SQLException("Erreur lors de l'initialisation du pool Druid : " + e.getMessage(), e);
        }
    }

    /* Getters et Setters pour l'injection par Spring */
    public String getDriver() { return driver; }
    public void setDriver(String driver) { this.driver = driver; }

    public String getURL() { return URL; }
    public void setURL(String URL) { this.URL = URL; }

    public String getUser() { return user; }
    public void setUser(String user) { this.user = user; }

    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }

    public int getMaxConnection() { return maxConnection; }
    public void setMaxConnection(int maxConnection) { this.maxConnection = maxConnection; }

    public String getValidationQuery() { return validationQuery; }
    public void setValidationQuery(String validationQuery) { this.validationQuery = validationQuery; }

    public boolean isValidateOnCheckout() { return validateOnCheckout; }
    public void setValidateOnCheckout(boolean validateOnCheckout) { this.validateOnCheckout = validateOnCheckout; }

    public int getIdleConnectionValidationSeconds() { return idleConnectionValidationSeconds; }
    public void setIdleConnectionValidationSeconds(int idleConnectionValidationSeconds) { this.idleConnectionValidationSeconds = idleConnectionValidationSeconds; }

    public DruidDataSource getDatasource() { return datasource; }
    public void setDatasource(DruidDataSource datasource) { this.datasource = datasource; }
}

Intégration de Spring dans les Jobs Quartz

Pour que vos tâches Quartz puissent utiliser les beans Spring, créez une fabrique de jobs personnalisée.

MyJobFactory.java


package com.qz.quartz02.utils;

import lombok.extern.slf4j.Slf4j;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

/**
 * Fabrique de jobs personnalisée pour permettre l'injection de dépendances Spring dans les Jobs Quartz.
 */
@Component
@Slf4j
public class MyJobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory beanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // Crée une instance du job en utilisant la fabrique de beans Spring
        Object jobInstance = super.createJobInstance(bundle);
        // Injecte les dépendances Spring dans l'instance du job
        beanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

Fichier de Configuration Quartz

Créez un fichier src/main/resources/quartz.properties pour configurer Quartz, en spécifiant notamment l'utilisation du pool Druid et le delegate JDBC.

quartz.properties


#============================================================================
# Propriétés principales du planificateur
#============================================================================
org.quartz.scheduler.instanceName: MonSchedulerQuartz
org.quartz.scheduler.instanceId: AUTO
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000

#============================================================================
# Configuration du JobStore (persistance en base de données)
#============================================================================
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties: true
org.quartz.jobStore.tablePrefix: qrtz_
org.quartz.jobStore.dataSource: qzDS
org.quartz.jobStore.isClustered: false # Mettez à true pour le mode cluster

#============================================================================
# Configuration de la source de données (DataSource)
#============================================================================
# Utilisation de notre fournisseur de connexion Druid personnalisé
org.quartz.dataSource.qzDS.connectionProvider.class: com.qz.quartz02.utils.DruidConnectionProvider
org.quartz.dataSource.qzDS.driver: com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
org.quartz.dataSource.qzDS.user: root
org.quartz.dataSource.qzDS.password: root
# Configuration spécifique à Druid via les setters de DruidConnectionProvider
org.quartz.dataSource.qzDS.maxConnection: 10 # Correspond à maxActive dans Druid
# org.quartz.dataSource.qzDS.validationQuery: SELECT 1 # Exemple de requête de validation

Configuration Spring pour Quartz

Créez une classe de configuration Spring pour initialiser Quartz avec votre fabrique de jobs et vos propriétés personnalisées.

QuartzConfiguration.java


package com.qz.quartz02.config;

import com.qz.quartz02.utils.MyJobFactory;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
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 java.io.IOException;
import java.util.Properties;

@Configuration
public class QuartzConfiguration {

    @Autowired
    private MyJobFactory myJobFactory;

    @Bean
    public SchedulerFactoryBean schedulerFactory() {
        SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
        try {
            // Charger les propriétés personnalisées de Quartz
            factoryBean.setQuartzProperties(quartzProperties());
            // Utiliser notre fabrique de jobs personnalisée
            factoryBean.setJobFactory(myJobFactory);
            // Important : S'assurer que le démarrage est géré par Spring
            factoryBean.setStartupDelay(1); // Optionnel: délai avant démarrage du scheduler
        } catch (IOException e) {
            throw new RuntimeException("Erreur lors du chargement des propriétés Quartz", e);
        }
        return factoryBean;
    }

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

    // Bean pour obtenir le Scheduler principal
    @Bean(name = "scheduler")
    public Scheduler scheduler() {
        return schedulerFactory().getScheduler();
    }
}

Génération de Code MyBatis

Utilisez MyBatis Generator pour générer le code d'accès aux données à partir de vos tables de base de données.

generatorConfig.xml


<?xml version="1.0" encoding="UTF-8" ?>
<generatorConfiguration>
    <!-- Fichier de propriétés pour la connexion JDBC -->
    <properties resource="jdbc.properties"/>

    <!-- Chemin vers le JAR du pilote JDBC -->
    <classPathEntry location="CHEMIN_VERS_VOTRE_MYSQL_CONNECTOR"/> <!-- Remplacez par le chemin réel -->

    <context id="quartzContext">
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
            <property name="suppressDate" value="true"/>
        </commentGenerator>

        <!-- Configuration de la connexion JDBC -->
        <jdbcConnection driverClass="${jdbc.driver}"
                        connectionURL="${jdbc.url}"
                        userId="${jdbc.username}"
                        password="${jdbc.password}"/>

        <!-- Configuration du type Java -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!-- Génération des Modèles (POJOs) -->
        <javaModelGenerator targetPackage="com.qz.quartz02.model" targetProject="src/main/java">
            <property name="enableSubPackages" value="false"/>
            <property name="constructorBased" value="true"/>
            <property name="trimStrings" value="false"/>
            <property name="immutable" value="false"/>
        </javaModelGenerator>

        <!-- Génération des fichiers de mapping SQL (XML) -->
        <sqlMapGenerator targetPackage="com.qz.quartz02.mapper" targetProject="src/main/java">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>

        <!-- Génération des interfaces Mapper -->
        <javaClientGenerator targetPackage="com.qz.quartz02.mapper" targetProject="src/main/java" type="XMLMAPPER">
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>

        <!-- Tables à mapper -->
        <table schema="" tableName="t_schedule_trigger" domainObjectName="ScheduleTrigger"
               enableCountByExample="false" enableDeleteByExample="false"
               enableSelectByExample="false" enableUpdateByExample="false">
            <property name="useActualColumnNames" value="true" />
        </table>
        <table schema="" tableName="t_schedule_trigger_param" domainObjectName="ScheduleTriggerParam"
               enableCountByExample="false" enableDeleteByExample="false"
               enableSelectByExample="false" enableUpdateByExample="false">
            <property name="useActualColumnNames" value="true" />
        </table>
    </context>
</generatorConfiguration>

jdbc.properties


jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root
# Ces propriétés peuvent être utilisées par d'autres pools de connexions si nécessaire
# jdbc.initialSize=10
# jdbc.maxTotal=100
# jdbc.maxIdle=50
# jdbc.minIdle=10
# jdbc.maxWaitMillis=-1

Exécutez le plugin MyBatis Generator (par exemple, via Maven : mvn mybatis-generator:generate) pour créer les modèles, les mappers et les fichiers XML correspondants.

Implémentation des Jobs

Créez vos classes de jobs, en vous asurant qu'elles implémentent l'interface org.quartz.Job.

MyJob.java (Job simple)


package com.qz.quartz02.job;

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Slf4j
public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.err.println("Exécution de MyJob simple - Heure : " + new Date().toLocaleString());
    }
}

MyJob1.java (Job avec JobDataMap)


package com.qz.quartz02.job;

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Slf4j
public class MyJob1 implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        System.out.println(new Date().toLocaleString() + " --> MyJob1: Nombre de paramètres reçus : " + dataMap.size());
    }
}

MyJob2.java (Job avec paramètres spécifiques)


package com.qz.quartz02.job;

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Slf4j
public class MyJob2 implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String name = (String) dataMap.get("name");
        Integer score = (Integer) dataMap.get("score");
        System.out.println(new Date().toLocaleString() + " --> MyJob2: Nom=" + name + ", Score=" + score);
    }
}

Couche Service et Contrôleur

Créez une couche service pour interagir avec la base de données et le planificateur Quartz, et un contrôleur pour gérer les requêtes web.

ScheduleTriggerService.java


package com.qz.quartz02.service;

import com.qz.quartz02.model.ScheduleTrigger; // Utilisez le modèle généré par MyBatis

import java.util.List;

public interface ScheduleTriggerService {
    int deleteByPrimaryKey(Integer id);
    int insert(ScheduleTrigger record);
    int insertSelective(ScheduleTrigger record);
    ScheduleTrigger selectByPrimaryKey(Integer id);
    int updateByPrimaryKeySelective(ScheduleTrigger record);
    int updateByPrimaryKey(ScheduleTrigger record);

    /**
     * Récupère la liste de tous les déclencheurs de tâches planifiées.
     * @return Liste des déclencheurs.
     */
    List<ScheduleTrigger> queryScheduleTriggerList();
}

ScheduleTriggerServiceImpl.java


package com.qz.quartz02.service.impl;

import com.qz.quartz02.mapper.ScheduleTriggerMapper;
import com.qz.quartz02.mapper.ScheduleTriggerParamMapper;
import com.qz.quartz02.model.ScheduleTrigger;
import com.qz.quartz02.model.ScheduleTriggerParam;
import com.qz.quartz02.service.ScheduleTriggerService;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ScheduleTriggerServiceImpl implements ScheduleTriggerService {

    @Autowired
    private ScheduleTriggerMapper scheduleTriggerMapper;

    @Autowired
    private ScheduleTriggerParamMapper scheduleTriggerParamMapper;

    @Autowired
    private Scheduler scheduler; // Injectez le Scheduler Spring

    /**
     * Rafraîchit le planificateur Quartz périodiquement en fonction des données de la base de données.
     */
    @Scheduled(cron = "0/15 * * * * ?") // Vérifie toutes les 15 secondes
    public void refreshScheduler() {
        try {
            List<ScheduleTrigger> triggersFromDb = scheduleTriggerMapper.queryScheduleTriggerList();

            if (triggersFromDb != null) {
                for (ScheduleTrigger triggerInfo : triggersFromDb) {
                    String jobClassName = triggerInfo.getJob_name();
                    String jobGroup = triggerInfo.getJob_group();
                    String cronExpression = triggerInfo.getCron();
                    String status = triggerInfo.getStatus(); // '1' pour actif, '0' pour inactif

                    JobKey jobKey = JobKey.jobKey(jobClassName, jobGroup);
                    TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroup);

                    CronTrigger existingTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);

                    if (status.equals("1")) { // Tâche active
                        if (existingTrigger == null) {
                            // Créer et planifier une nouvelle tâche
                            System.out.println("Création et planification du job: " + jobClassName);
                            JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(jobClassName))
                                    .withIdentity(jobKey)
                                    .build();

                            // Ajouter les paramètres au JobDataMap
                            List<ScheduleTriggerParam> params = scheduleTriggerParamMapper.queryScheduleParamLst(triggerInfo.getId());
                            if (params != null && !params.isEmpty()) {
                                JobDataMap jobDataMap = jobDetail.getJobDataMap();
                                for (ScheduleTriggerParam param : params) {
                                    jobDataMap.put(param.getName(), param.getValue());
                                }
                            }

                            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
                            Trigger newTrigger = TriggerBuilder.newTrigger()
                                    .withIdentity(triggerKey)
                                    .withSchedule(scheduleBuilder)
                                    .build();

                            scheduler.scheduleJob(jobDetail, newTrigger);
                        } else {
                            // Mettre à jour le trigger existant si l'expression cron a changé
                            String existingCron = existingTrigger.getCronExpression();
                            if (!cronExpression.equals(existingCron)) {
                                System.out.println("Mise à jour de l'expression cron pour le job: " + jobClassName);
                                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
                                CronTrigger updatedTrigger = existingTrigger.getTriggerBuilder()
                                        .withIdentity(triggerKey)
                                        .withSchedule(scheduleBuilder)
                                        .build();
                                scheduler.rescheduleJob(triggerKey, updatedTrigger);
                            }
                        }
                    } else { // Tâche inactive (statut '0')
                        if (existingTrigger != null) {
                            // Supprimer la tâche si elle existe
                            System.out.println("Suppression du job inactif: " + jobClassName);
                            scheduler.deleteJob(jobKey);
                        }
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            System.err.println("Erreur : Classe du job non trouvée. Vérifiez le nom complet de la classe. " + e.getMessage());
            // Gérer l'erreur : log, lancer une exception, etc.
        } catch (SchedulerException e) {
            System.err.println("Erreur Scheduler Quartz: " + e.getMessage());
            // Gérer l'erreur : log, lancer une exception, etc.
        } catch (Exception e) {
            System.err.println("Erreur inattendue lors de la synchronisation des tâches: " + e.getMessage());
            e.printStackTrace();
        }
    }

    // Implémentation des méthodes CRUD de ScheduleTriggerMapper
    @Override
    public int deleteByPrimaryKey(Integer id) { return scheduleTriggerMapper.deleteByPrimaryKey(id); }
    @Override
    public int insert(ScheduleTrigger record) { return scheduleTriggerMapper.insert(record); }
    @Override
    public int insertSelective(ScheduleTrigger record) { return scheduleTriggerMapper.insertSelective(record); }
    @Override
    public ScheduleTrigger selectByPrimaryKey(Integer id) { return scheduleTriggerMapper.selectByPrimaryKey(id); }
    @Override
    public int updateByPrimaryKeySelective(ScheduleTrigger record) { return scheduleTriggerMapper.updateByPrimaryKeySelective(record); }
    @Override
    public int updateByPrimaryKey(ScheduleTrigger record) { return scheduleTriggerMapper.updateByPrimaryKey(record); }
    @Override
    public List<ScheduleTrigger> queryScheduleTriggerList() { return scheduleTriggerMapper.queryScheduleTriggerList(); }
}

QuartzController.java


package com.qz.quartz02.controller;

import com.qz.quartz02.model.ScheduleTrigger;
import com.qz.quartz02.service.ScheduleTriggerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
@RequestMapping("/quartz")
public class QuartzController {

    @Autowired
    private ScheduleTriggerService scheduleTriggerService;

    /**
     * Affiche la liste des tâches planifiées.
     */
    @GetMapping("/list")
    public ModelAndView listQuartzJobs() {
        ModelAndView mv = new ModelAndView("list"); // Nom du template Thymeleaf
        List<ScheduleTrigger> jobList = scheduleTriggerService.queryScheduleTriggerList();
        mv.addObject("quartzList", jobList);
        return mv;
    }

    /**
     * Met à jour le statut (activation/désactivation) d'une tâche.
     */
    @GetMapping("/editStatus")
    public String editJobStatus(ScheduleTrigger scheduleTrigger) {
        scheduleTriggerService.updateByPrimaryKeySelective(scheduleTrigger);
        // La logique de rafraîchissement sera déclenchée par @Scheduled
        return "redirect:/quartz/list";
    }

    /**
     * Affiche le formulaire d'édition d'une tâche.
     */
    @GetMapping("/edit/{id}")
    public ModelAndView editJobForm(@PathVariable Integer id) {
        ModelAndView mv = new ModelAndView("edit"); // Nom du template Thymeleaf
        ScheduleTrigger scheduleTrigger = scheduleTriggerService.selectByPrimaryKey(id);
        mv.addObject("schedule", scheduleTrigger);
        return mv;
    }

    /**
     * Sauvegarde les modifications d'une tâche.
     */
    @PostMapping("/save")
    public String saveJob(ScheduleTrigger scheduleTrigger) {
        scheduleTriggerService.updateByPrimaryKeySelective(scheduleTrigger);
        // La logique de rafraîchissement sera déclenchée par @Scheduled
        return "redirect:/quartz/list";
    }
}

Interfaces Utilisateur (Thymeleaf)

Créez les fichiers HTML pour l'affichage et la gestion des tâches.

src/main/resources/templates/list.html



<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Gestion des Tâches Quartz</title>
    <style>
        body { font-family: sans-serif; }
        table { border-collapse: collapse; width: 80%; margin: 20px auto; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        .actions a { margin-right: 10px; text-decoration: none; }
        .status-active { color: green; }
        .status-inactive { color: red; }
    </style>
</head>
<body>
    <h1 style="text-align: center;">Gestion des Tâches Planifiées</h1>

    <table>
        <tr>
            <th>ID</th>
            <th>Expression Cron</th>
            <th>Statut</th>
            <th>Classe du Job</th>
            <th>Groupe</th>
            <th>Actions</th>
        </tr>
        <tr th:each="job : ${quartzList}">
            <td th:text="${job.id}"></td>
            <td th:text="${job.cron}"></td>
            <td>
                <span th:if="${job.status == '1'}" class="status-active">Actif</span>
                <span th:if="${job.status == '0'}" class="status-inactive">Inactif</span>
            </td>
            <td th:text="${job.job_name}"></td>
            <td th:text="${job.job_group}"></td>
            <td class="actions">
                <a th:if="${job.status == '0'}" th:href="@{/quartz/editStatus(id=${job.id},status='1')}">Activer</a>
                <a th:if="${job.status == '1'}" th:href="@{/quartz/editStatus(id=${job.id},status='0')}">Désactiver</a>
                <a th:href="@{'/quartz/edit/' + ${job.id}}">Modifier</a>
            </td>
        </tr>
    </table>
</body>
</html>

src/main/resources/templates/edit.html



<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Modifier la Tâche Planifiée</title>
    <style>
        body { font-family: sans-serif; padding: 20px; }
        form { width: 400px; margin: 20px auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px;}
        label { display: block; margin-bottom: 5px; }
        input[type=text], input[type=submit] {
            width: calc(100% - 12px);
            padding: 8px;
            margin-bottom: 15px;
            border: 1px solid #ccc;
            border-radius: 3px;
        }
        input[type=submit] {
            background-color: #4CAF50;
            color: white;
            cursor: pointer;
        }
        input[type=submit]:hover { background-color: #45a049; }
    </style>
</head>
<body>
    <h1>Modifier la Tâche Planifiée</h1>
    <form th:action="@{/quartz/save}" method="post">
        <input type="hidden" name="id" th:value="${schedule.id}" />

        <label for="cron">Expression Cron</label>
        <input type="text" id="cron" name="cron" th:value="${schedule.cron}" required /><br>

        <label for="job_name">Classe du Job (Complet)</label>
        <input type="text" id="job_name" name="job_name" th:value="${schedule.job_name}" required /><br>

        <label for="job_group">Groupe</label>
        <input type="text" id="job_group" name="job_group" th:value="${schedule.job_group}" required /><br>

        <input type="submit" value="Sauvegarder les modifications" />
    </form>
</body>
</html>

Après configuration, les tâches planifiées seront chargées depuis la base de données et pourront être gérées via l'interface web.

Étiquettes: Spring Boot Quartz MySQL Java Planificateur de tâches

Publié le 11 juin à 21h01