quartz timed task cluster version

Posted by Jakebert on Thu, 25 Nov 2021 03:52:23 +0100

Opening note

  • If you have been inspired and thought here, I hope you can praise and support! You have different views on the content. Welcome to write and exchange.
  • Technology stack > > java
  • Mailbox > > 15673219519@163.com

describe

  • The previous project has just started. It is easy to realize the stand-alone version through. quartz timing task, two tables to achieve data persistence
  • As the project has been updated iteratively for more than half a year, more regular tasks need to be performed. In addition, there are other services in the service. The scheduled tasks of the stand-alone version affect the cluster construction of the service. Therefore, we should improve it.
  • The goal is to retain the use of two tables in the stand-alone version to control the scheduled task execution plan on the page and view the execution results of each task.

My thoughts

  • The 11 data tables officially provided by quartz are used as data storage to realize the data sharing of cluster services.
  • Custom table schedule_ The job is used as the initialization of the task and the subsequent modification of the execution plan, and the user-defined table schedule_log is used as the storage of execution records. This is consistent with the stand-alone version.
  • The scheduled task listener ScheduleJobListener.java records the execution results of the task.
  • Note: unlike the stand-alone version, the data of the cluster version is not stored in memory, but in the 11 tables officially provided. Since multiple services will be started, it is necessary to judge whether the scheduled task already exists when initializing the task. (during stand-alone operation, the data in memory will be emptied every time it is shut down, while the data in the database in the cluster will remain at the same value unless the table is recreated). Therefore, we need to modify the ScheduleJobUtil tool class and the logic during initialization.
  • In addition, we need to add some configuration of data sources.

Step 1: add dependency

<!--quartz rely on-->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.3</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz-jobs</artifactId>
    <version>2.2.3</version>
</dependency>

Step 2: create a data table

  • For two customized tables, refer to the stand-alone blog in the beginning
  • All 11 tables are officially provided
-- Data sheets required for scheduled tasks
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_SIMPROP_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(
SCHED_NAME VARCHAR(120) NOT NULL,
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_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

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,
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 (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
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 (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

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(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_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_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;

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

CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);

commit;

Step 3: java code implementation

  • Add configuration quartz.properties
#quartz cluster configuration
#Scheduling ID name each instance in the cluster must use the same name
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#The ID is set to get automatically, and each must be different
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.makeSchedulerThreadDaemon=true
#Implementation class of thread pool (generally, SimpleThreadPool can meet the requirements)
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#Specifies whether the thread created in the thread pool is a daemon thread
org.quartz.threadPool.makeThreadsDaemons=true
#Specify the number of threads, at least 1 (no default)
org.quartz.threadPool.threadCount:20
#Set the priority of the thread (the maximum is java.lang.Thread.MAX_PRIORITY 10, the minimum is Thread.MIN_PRIORITY 1, and the default is 5)
org.quartz.threadPool.threadPriority:5
#The data storage method is database persistence
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#The database proxy class, generally org.quartz.impl.jdbc jobstore.stdjdbc delegate, can satisfy most databases
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#Table prefix, default QRTZ_
org.quartz.jobStore.tablePrefix=QRTZ_
#Join the cluster
org.quartz.jobStore.isClustered=true
# The default value of information saving time is 60 seconds
org.quartz.jobStore.misfireThreshold=25000
  • ScheduleJobListener.java scheduled task listener: records the execution records of scheduled tasks by listening.
import com.qykj.admin.service.schedule.JobService;
import com.qykj.core.constants.schedule.JobLogStatusEnum;
import com.qykj.core.util.DateUtil;
import com.qykj.core.util.SpringUtils;
import org.apache.logging.log4j.ThreadContext;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.springframework.stereotype.Component;
import java.util.UUID;

/**
 * =====================================================================================================================
 * jiangshaoneng <15673219519.@163.com> 2021/11/20 14:18
 *
 * Scheduled task listener: records the execution records of scheduled tasks by listening.
 * =====================================================================================================================
 */
@Component
public class ScheduleJobListener implements JobListener{

    private JobService jobService;

    // Used to save the logId
    private ThreadLocal<String> threadLocalLogId = new ThreadLocal<>();
    // startTime
    private ThreadLocal<Long> threadLocalStartTime = new ThreadLocal<>();

    @Override
    public String getName() {
        return "myJobListener";
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
        ThreadContext.put("traceID", UUID.randomUUID().toString().replace("-",""));
        if(jobService == null){
            jobService = SpringUtils.getBean(JobService.class);
        }
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        String name = jobDetail.getKey().getName();
        String logId = jobService.saveScheduleLog(name, JobLogStatusEnum.RUNNING.getCode(), "");
        threadLocalLogId.set(logId); // Put the execution record in threadLocal and provide it to get the result after execution
        threadLocalStartTime.set(DateUtil.getCurrentTime());
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
        String logId = threadLocalLogId.get(); // Execution record logId
        long runTime = DateUtil.getCurrentTime() - threadLocalStartTime.get();
        jobService.updateScheduleLog(logId, JobLogStatusEnum.ERROR.getCode(), "Execution failed",runTime);
    }


    @Override
    public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
        String logId = threadLocalLogId.get(); // Execution record logId
        long runTime = DateUtil.getCurrentTime() - threadLocalStartTime.get();
        if(e == null){ // No exceptions are recorded as successful
            jobService.updateScheduleLog(logId, JobLogStatusEnum.SUCCESS.getCode(), "Successful execution",runTime);
        }else{ // There are exceptions. Failed to modify the record, and save the exception information to the database
            jobService.updateScheduleLog(logId, JobLogStatusEnum.ERROR.getCode(), e.toString(), runTime);
        }
    }
}
  • ScheduleConfig.java scheduled task configuration class, configure data source, listener and other information
import com.qykj.admin.job.ScheduleJobListener;
import com.qykj.admin.job.ScheduleJobUtil;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
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.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.Executor;
import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;

/**
 * =====================================================================================================================
 * jiangshaoneng <15673219519.@163.com> 22021/11/20 14:18
 *
 * Scheduled task configuration class.
 * =====================================================================================================================
 */
@Configuration
public class SchedulerConfig {

    @Qualifier("writeDataSource")
    @Autowired
    private DataSource dataSource;

    @Bean
    public Scheduler scheduler() throws IOException{
        Scheduler scheduler = schedulerFactoryBean().getScheduler();
        try {
            // MyJobListener is bound to job by default_ GROUP_ Name, record the execution record of scheduled tasks by listening in MyJobListener
            scheduler.getListenerManager()
                    .addJobListener(new ScheduleJobListener(), jobGroupEquals(ScheduleJobUtil.JOB_GROUP_NAME));
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return scheduler;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException{
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setSchedulerName("cluster_scheduler");
        factory.setDataSource(dataSource);
        factory.setApplicationContextSchedulerContextKey("application");
        factory.setQuartzProperties(quartzProperties());
        factory.setTaskExecutor(schedulerThreadPool());
        factory.setStartupDelay(0);
        return factory;
    }

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

    @Bean
    public Executor schedulerThreadPool(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
        executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors());
        executor.setQueueCapacity(Runtime.getRuntime().availableProcessors());
        return executor;
    }
}
  • ScheduleJobUtils.java scheduled task tool class, including the addition, deletion, modification and query of tasks and the configuration of the database. Therefore, all operations are aimed at the operations in the database. (the jar package has been integrated. You only need to call the corresponding method, and we don't need to implement database operation)
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * =====================================================================================================================
 * jiangshaoneng <15673219519.@163.com> 2021/11/20 14:18
 *
 * Tool class for task scheduling, including addition, modification and deletion of scheduled tasks. The system temporarily uses the same name for task group and trigger group.
 * For example: JOB_GROUP_NAME,TRIGGER_GROUP_NAME
 * =====================================================================================================================
 */
@Slf4j
@Component
public class ScheduleJobUtil {

    @Autowired
    private Scheduler scheduler;

    public static String JOB_GROUP_NAME = "DEFAULT_JOB_GROUP";
    private static String TRIGGER_GROUP_NAME = "DEFAULT_TRIGGER_GROUP";

    public ScheduleJobUtil(){

    }

    /**
     * @Description: Add a scheduled task and use the default task group name, trigger name and trigger group name
     * @param jobName Task name
     * @param cls task
     * @param cron For time setting, refer to the quartz documentation
     * @throws SchedulerException
     */
    public void addJob(String jobName, Class cls, String cron, JobDataMap dataMap) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);
        Trigger trigger = scheduler.getTrigger(triggerKey);
        if(trigger != null){
            log.info("Add task:{},{},{} Already exists", jobName, cls, cron);
            return;
        }

        // It is used to describe the Job implementation class and other static information and build a Job instance
        JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, JOB_GROUP_NAME).build();
        // Build a trigger and specify the trigger rules
        trigger = TriggerBuilder.newTrigger()// Create a new trigger builder to standardize a trigger
                .withIdentity(jobName, TRIGGER_GROUP_NAME)// Give the trigger a name and group name
                .startNow()// Execute now
                .withSchedule(CronScheduleBuilder.cronSchedule(cron)) // Execution time of trigger
                .usingJobData(dataMap) // Timer some simple data
                .build();// Generate trigger
        scheduler.scheduleJob(jobDetail, trigger);
        log.info("Add task:{},{},{}", jobName, cls, cron);
        // start-up
        if (!scheduler.isShutdown()) {
            scheduler.start();
        }
    }

    /**
     * @Description: Add a scheduled task
     *
     * @param jobName Task name
     * @param jobGroupName Task group name
     * @param triggerName Trigger Name 
     * @param triggerGroupName Trigger group name
     * @param cls task
     * @param cron For time setting, refer to the quartz documentation
     */
    public void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class cls, String cron) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);
        Trigger trigger = scheduler.getTrigger(triggerKey);
        if(trigger != null){
            log.info("Add task:{},{},{},{},{},{} Already exists",jobName,jobGroupName,triggerName,triggerGroupName,cls,cron);
            return;
        }

        // It is used to describe the Job implementation class and other static information and build a Job instance
        JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, jobGroupName).build();
        // Build a trigger and specify the trigger rules
        trigger = TriggerBuilder.newTrigger()// Create a new trigger builder to standardize a trigger
                .withIdentity(jobName, triggerGroupName)// Give the trigger a name and group name
                .startNow()// Execute now
                .withSchedule(CronScheduleBuilder.cronSchedule(cron)) // Execution time of trigger
                .build();// Generate trigger
        scheduler.scheduleJob(jobDetail, trigger);
        log.info("Add task:{},{},{},{},{},{}",jobName,jobGroupName,triggerName,triggerGroupName,cls,cron);
        // start-up
        if (!scheduler.isShutdown()) {
            scheduler.start();
        }
    }

    /**
     * @Description: Modify the trigger time of a task (use the default task group name, trigger name, trigger group name)
     *
     * @param jobName
     * @param cron
     * @throws SchedulerException
     */
    public void modifyJobTime(String jobName, String cron, JobDataMap dataMap) throws SchedulerException {
        TriggerKey triggerKey = new TriggerKey(jobName, TRIGGER_GROUP_NAME);
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (trigger == null) {
            log.info("Failed to modify task,This task does not exist:{},{}", jobName, cron);
            return;
        }
        String oldTime = trigger.getCronExpression();
        if (!oldTime.equalsIgnoreCase(cron)) {
            JobDetail jobDetail = scheduler.getJobDetail(new JobKey(jobName, JOB_GROUP_NAME));
            Class objJobClass = jobDetail.getJobClass();
            removeJob(jobName);
            addJob(jobName, objJobClass, cron, dataMap);
            log.info("Modify task:{},{}",jobName,cron);
        }
    }

    /**
     * @Description: Modify or add a task. If jobName exists, modify the task. If it does not exist, add a new task
     *
     */
    public void modifyOrAddJobTime(String jobName, Class cls, String cron, JobDataMap dataMap) throws SchedulerException {
        TriggerKey triggerKey = new TriggerKey(jobName, TRIGGER_GROUP_NAME);
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (trigger == null) {
            addJob(jobName, cls, cron, dataMap);
            log.info("Add task:{},{}",jobName,cron);
        }else{
            String oldTime = trigger.getCronExpression();
            JobDataMap oldJobDataMap = trigger.getJobDataMap();
            if (!oldTime.equalsIgnoreCase(cron) || !oldJobDataMap.equals(dataMap)) {
                JobDetail jobDetail = scheduler.getJobDetail(new JobKey(jobName, JOB_GROUP_NAME));
                Class objJobClass = jobDetail.getJobClass();
                removeJob(jobName);
                addJob(jobName, objJobClass, cron, dataMap);
                log.info("Modify task:{},{}",jobName,cron);
            }else {
                log.info("Task exists:{},{}",jobName,cron);
            }
        }
    }

    /**
     * @Description: Remove a task (use the default task group name, trigger name, trigger group name)
     * @param jobName
     * @throws SchedulerException
     */
    public void removeJob(String jobName) throws SchedulerException {
        JobKey jobKey = new JobKey(jobName, TRIGGER_GROUP_NAME);
        // Stop trigger
        scheduler.pauseJob(jobKey);
        scheduler.unscheduleJob(new TriggerKey(jobName, TRIGGER_GROUP_NAME));// Remove trigger
        scheduler.deleteJob(jobKey);// Delete task
        log.info("Remove task:{}",jobName);
    }

    /**
     * Remove task
     *
     * @param jobName
     * @param jobGroupName
     * @param triggerName
     * @param triggerGroupName
     * @throws SchedulerException
     */
    public void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) throws SchedulerException {
        JobKey jobKey = new JobKey(jobName, jobGroupName);
        // Stop trigger
        scheduler.pauseJob(jobKey);
        scheduler.unscheduleJob(new TriggerKey(jobName, triggerGroupName));// Remove trigger
        scheduler.deleteJob(jobKey);// Delete task
        log.info("Remove task:{},{},{},{}",jobName,jobGroupName,triggerName,triggerGroupName);
    }

    /**
     * Start all tasks
     * @throws SchedulerException
     */
    public void startJobs() throws SchedulerException {
        scheduler.start();
        log.info("Start all tasks");
    }

    /**
     * Close all scheduled tasks
     * @throws SchedulerException
     */
    public void shutdownJobs() throws SchedulerException {
        if (!scheduler.isShutdown()) {
            scheduler.shutdown();
            log.info("Close all tasks");
        }
    }
}
  • ScheduleJobInitListener.java initializes scheduled tasks when listening to project startup
import com.qykj.admin.service.schedule.JobService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

/**
 * =====================================================================================================================
 * jiangshaoneng <15673219519.@163.com> 2021/11/20 14:18
 *
 * When you start a project, start the scheduled task of the database configuration. Can support centralized deployment
 * =====================================================================================================================
 */
@Slf4j
@Component
public class ScheduleJobInitListener implements ApplicationListener<ContextRefreshedEvent>{

    @Autowired
    private JobService jobService;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        log.info("Start initializing scheduled tasks ...");
        jobService.initScheduleJob();
        log.info("Initialization of scheduled task succeeded ...");
    }
}

  • ScheduleService.java timing task initialization, modification and other logic
import com.qykj.admin.job.ScheduleJobUtil;
import com.qykj.core.domain.entity.schedule.ScheduleJob;
import com.qykj.core.domain.entity.schedule.ScheduleLog;
import com.qykj.core.exception.AppException;
import com.qykj.core.exception.ErrorCode;
import com.qykj.core.util.JsonUtils;
import com.qykj.repo.impl.schedule.ScheduleJobRepoImpl;
import com.qykj.repo.impl.schedule.ScheduleLogRepoImpl;
import org.quartz.JobDataMap;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class JobService {

    private static final Logger logger = LoggerFactory.getLogger(JobService.class);

    @Autowired
    private ScheduleJobRepoImpl scheduleJobRepo;
    @Autowired
    private ScheduleLogRepoImpl scheduleLogRepo;
    @Autowired
    private ScheduleJobUtil scheduleJobUtil;
    /**
     * View all task lists
     */
    public List<ScheduleJob> getAllJob(){
        return scheduleJobRepo.getList();
    }

    /**
     * View the execution details of a task
     */
    public List<ScheduleLog> getJobScheduleDetail(int jobId){
        return scheduleLogRepo.getListByJobId(jobId);
    }

    /**
     * View the details of a task
     */
    public ScheduleJob getJobDetail(int jobId){
        return scheduleJobRepo.getScheduleJobById(jobId);
    }

    /**
     * Modify task
     */
    public Map<String,Object> updateJob(ScheduleJob scheduleJob){
        Map<String,Object> result = new HashMap<>();

        try {
            Class<?> clszz = Class.forName(scheduleJob.getClazz());
            Integer status = scheduleJob.getStatus();

            if(status == 1){ // Modify or add tasks
                Map<String, String> map = (Map<String, String>) JsonUtils.json2Map(scheduleJob.getJobDataJson());
                JobDataMap dataMap = new JobDataMap(map); // Data required to configure the timer
                scheduleJobUtil.modifyOrAddJobTime(scheduleJob.getName(), clszz, scheduleJob.getCron(), dataMap);

            }else if(status == 2){ // Stop task
                scheduleJobUtil.removeJob(scheduleJob.getName());
            }else{
                return result;
            }
        }catch (SchedulerException e){
            logger.error("Modify scheduled task exception:{}", e.toString());
            throw new AppException(ErrorCode.SYS_ERROR);
        }catch (ClassNotFoundException e){
            logger.error("Exception in modifying scheduled task. The specified task class cannot be found");
            throw new AppException(ErrorCode.SYS_PARAMS_ERROR.code(), "The specified task class cannot be found");
        }

        scheduleJobRepo.updateScheduleJob(scheduleJob);
        return result;
    }

    /**
     * Start scheduled tasks configured in the database
     */
    public void initScheduleJob(){
        // Remove scheduled tasks that do not need to be performed
        List<ScheduleJob> disableList = scheduleJobRepo.getDisableList();
        for (ScheduleJob scheduleJob: disableList){
            try {
                scheduleJobUtil.removeJob(scheduleJob.getName());
            } catch (SchedulerException e) {
                logger.error("Remove scheduled tasks:{}Exception:{}", scheduleJob.getName(), e.toString());
            }
        }

        // Add scheduled tasks to be executed
        List<ScheduleJob> enableList = scheduleJobRepo.getEnableList();
        for (ScheduleJob scheduleJob: enableList){
            Map<String, String> map = new HashMap<>();
            try {
                map = (Map<String, String>) JsonUtils.json2Map(scheduleJob.getJobDataJson());
            }catch (Exception e){
                logger.error("Scheduled tasks:{},DataJson Illegal format", scheduleJob.getName());
            }
            try {
                JobDataMap dataMap = new JobDataMap(map); // Data required to configure the timer
                Class<?> clszz = Class.forName(scheduleJob.getClazz());
                scheduleJobUtil.modifyOrAddJobTime(scheduleJob.getName(), clszz, scheduleJob.getCron(), dataMap);
            } catch (SchedulerException e){
                logger.error("Initialize start scheduled task:{},Exception:{}", scheduleJob.getName(), e.toString());
                continue;
            } catch (ClassNotFoundException e){
                logger.error("Initialization start timing task exception. The specified task class cannot be found");
                continue;
            }
        }
    }

    /**
     * Add an execution log
     */
    public String saveScheduleLog(String name, int status, String logInfo){
        ScheduleJob scheduleJob = scheduleJobRepo.getScheduleJobByName(name);
        ScheduleLog scheduleLog = new ScheduleLog(scheduleJob,status,logInfo);
        scheduleLogRepo.saveScheduleLog(scheduleLog);
        return scheduleLog.getId();
    }

    /**
     * Update an execution log
     */
    public void updateScheduleLog(String id, int status, String logInfo, long runTime){
        scheduleLogRepo.updateScheduleLog(id,status,logInfo,runTime);
    }
}
  • ScheduleController.java provides an interface for page operation
import com.qykj.admin.aspect.AuthPermissions;
import com.qykj.admin.service.schedule.JobService;
import com.qykj.core.domain.entity.schedule.ScheduleJob;
import com.qykj.core.domain.entity.schedule.ScheduleLog;
import com.qykj.core.view.MSG;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/v2/schedule/job")
@Api(tags = "Schedule:001-Scheduled task control management")
public class JobControllerV2 {

    @Autowired
    private JobService jobService;

    @ApiOperation(value = "View all task lists")
    @GetMapping("/list")
    public MSG<List<ScheduleJob>> getJobList(){
        List<ScheduleJob> allJob = jobService.getAllJob();
        return MSG.SUCCESS(allJob);
    }

    @ApiOperation(value = "View the execution details of a task")
    @GetMapping("/schedule/detail")
    public MSG<List<ScheduleLog>> getJobScheduleDetail(@RequestParam("jobId") int jobId){
        List<ScheduleLog> jobScheduleDetail = jobService.getJobScheduleDetail(jobId);
        return MSG.SUCCESS(jobScheduleDetail);
    }

    @ApiOperation(value = "View the details of a task")
    @GetMapping("/detail")
    public MSG<ScheduleJob> getJobDetail(@RequestParam("jobId") int jobId){
        ScheduleJob jobDetail = jobService.getJobDetail(jobId);
        return MSG.SUCCESS(jobDetail);
    }

    @ApiOperation(value = "Modify task")
    @PostMapping("/update")
    public MSG updateJob(@RequestBody ScheduleJob job){
        jobService.updateJob(job);
        return MSG.SUCCESS();
    }
}

Topics: Java Spring Boot Quartz