Detailed design of scheduled tasks

Posted by Johnm on Tue, 21 Apr 2020 16:40:36 +0200

Detailed design of scheduled tasks

Timing task is implemented by QuartZ, and QuartZ document reference https://www.w3cschool.cn/quartz_doc/

QuartZ configuration

  1. Create a new QuartZ data table in the database. The corresponding SQL table is in the QuartZ.sql file in the current directory
  2. Add the quartz.properties file, which provides the related configuration of QuartZ internally
# Fixed prefix org.quartz
# It is mainly divided into scheduler, threadPool, jobStore, plugin, etc
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

# When instantiating ThreadPool, the thread class used is SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

# threadCount and threadPriority will be injected into the ThreadPool instance as setter s
# Number of concurrent
org.quartz.threadPool.threadCount = 5
# priority
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

org.quartz.jobStore.misfireThreshold = 60000
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

#Persistence
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.dataSource = shop_mrg_admin_new

org.quartz.dataSource.shop_mrg_admin_new.driver = @datasource.driver@

org.quartz.dataSource.shop_mrg_admin_new.URL = @datasource.url@

org.quartz.dataSource.shop_mrg_admin_new.user = @datasource.username@

org.quartz.dataSource.shop_mrg_admin_new.password = @datasource.password@

org.quartz.dataSource.shop_mrg_admin_new.maxConnections = 10

# trigger execution log display
org.quartz.plugin.triggHistory.class =org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.triggHistory.triggerFiredMessage =Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}
org.quartz.plugin.triggHistory.triggerCompleteMessage =Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy}.
  1. Add QuartZ configuration JobFactory, quartzconfiguration
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;

@Component
public class JobFactory extends AdaptableJobFactory {
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception{
        Object jobInstance = super.createJobInstance(bundle);
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }

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

@Configuration
public class QuartzConfigration {
    @Autowired
    private JobFactory jobFactory;

    //Get factory bean
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        try {
            schedulerFactoryBean.setQuartzProperties(quartzProperties());
            //Update existing job s at startup
            schedulerFactoryBean.setOverwriteExistingJobs(true);
            //Auto start
            schedulerFactoryBean.setAutoStartup(true);
            schedulerFactoryBean.setJobFactory(jobFactory);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return schedulerFactoryBean;
    }

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

    //Create schedule
    @Bean(name = "scheduler")
    public Scheduler scheduler() {
        return schedulerFactoryBean().getScheduler();
    }

}
  1. Create a new job information table t ﹣ job ﹣ triggers in the database, and provide the basic operation of adding, deleting, modifying and querying the table (in the next operation, you need to use the query operation of t ﹣ job ﹣ triggers table). The sql of t ﹣ job ﹣ triggers is as follows:
CREATE TABLE `t_job_triggers` (
  `job_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
  `cron` varchar(100) COLLATE utf8_bin NOT NULL COMMENT 'cron expression',
  `status` int(11) NOT NULL COMMENT 'job Status 0=Not enabled, 1=Enable',
  `job_name` varchar(100) COLLATE utf8_bin NOT NULL COMMENT 'Scheduled task class name',
  `job_group` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT 'For and job_name Determine the solution together job Uniqueness of',
  `job_decs` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT 'Task description',
  `job_param` varchar(300) COLLATE utf8_bin DEFAULT NULL COMMENT 'Customizable additional parameters',
  PRIMARY KEY (`job_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=110 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
  1. Add a spring Scheduled to get the job data in the t job triggers. An example is as follows:
import com.gkcx.fls.dao.TJobTriggers;
import com.gkcx.fls.mappers.TJobTriggersMapper;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 ScheduleTriggerService {
    private static final Logger logger = LoggerFactory.getLogger(ScheduleTriggerService.class);

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private TJobTriggersMapper jobTriggersMapper;//Table Mapper

    //Update tasks in quartz regularly every other minute
    @Scheduled(cron = "${scheduler.cron.trigger}")
    public void refreshTrigger() {
        try {
            //Query all scheduled tasks in the database
            List<TJobTriggers> jobList = jobTriggersMapper.selectAll();
            if (jobList != null) {
                for (TJobTriggers jobTrigger : jobList) {

                    TriggerKey triggerKey = TriggerKey.triggerKey(jobTrigger.getJobName(), jobTrigger.getJobGroup());
                    CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);

                    //This task has not been added to quartz
                    if (cronTrigger == null) {
                        if (jobTrigger.getStatus() == 0) { //Task is not enabled
                            continue;
                        }
                        //Create a JobDetail (the full path of the task saved in the job [name] database, where you can dynamically inject the task into the JobDetail)
                        JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(jobTrigger.getJobName()))
                                .withIdentity(jobTrigger.getJobName(), jobTrigger.getJobGroup()).requestRecovery(true).build();
                        //Expression scheduling builder
                        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobTrigger
                                .getCron());
                        //Build a new trigger with a new cronExpression expression
                        cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobTrigger.getJobName(), jobTrigger.getJobGroup()).withSchedule(scheduleBuilder).build();
                        //Inject trigger and jobDetail into the scheduler
                        scheduler.scheduleJob(jobDetail, cronTrigger);

                    } else { //This task is already in quartz
                        //cronTrigger already exists. Judge whether to delete it first. If not, judge whether the time changes again
                        if (jobTrigger.getStatus() == 0) { //If disabled, remove this task from quartz
                            JobKey jobKey = JobKey.jobKey(jobTrigger.getJobName(), jobTrigger.getJobGroup());
                            scheduler.deleteJob(jobKey);
                            continue;
                        }
                        String searchCron = jobTrigger.getCron(); //Get cron expression in database
                        String currentCron = cronTrigger.getCronExpression();
                        if (!searchCron.equals(currentCron)) {  //It indicates that the task has changed and the corresponding records in quartz need to be updated
                            //Expression scheduling builder
                            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(searchCron);

                            //Rebuild trigger with new cronExpression expression
                            cronTrigger = cronTrigger.getTriggerBuilder().withIdentity(triggerKey)
                                    .withSchedule(scheduleBuilder).build();

                            //Reset job execution by new trigger
                            scheduler.rescheduleJob(triggerKey, cronTrigger);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.info("Scheduled task refresh Quartz in job Abnormal!");
            e.printStackTrace();
        }
    }

}

Developer documentation

Timing task source file location: you can create a new schedule package, for example: com.gkcx.fls.schedule

New scheduled task method

  1. Create a new class in com.gkcx.fls.schedule.job and implement the org.quartz.Job interface. Refer to the following example (TestTask.java):
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class TestTask implements Job {
    private static final Logger logger = LoggerFactory.getLogger(TestTask.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        logger.info("TestTask Start executing....");
        try{
            CronTrigger trigger = (CronTrigger) context.getTrigger();
            logger.info("cron:{}", trigger.getCronExpression());
            logger.info("jobName:{}", trigger.getKey().getName());
            logger.info("jobGroup:{}", trigger.getKey().getGroup());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            logger.info("TestTask End of execution....");
        }
    }
}
  1. Add the new class information to the t job triggers in the database. Pay attention to the correctness of cron expression and job name, otherwise it will affect the operation of the scheduled task. Refer to the following example:
INSERT INTO t_job_triggers (job_id, cron, status, job_name, job_group, job_decs, job_param) VALUES(100, '0 21 10 * * ?', 0, 'com.gkcx.fls.schedule.job.TestTask', 'test', 'For testing', NULL);

be careful:

The table beginning with QRTZ in the database is the table required for QUARTZ persistence. Please do not modify it. When you migrate scheduled tasks to the production environment, you need to add tables that start with QRTZ to the production database.

Topics: Programming Database Java SQL Spring