quartz timing task, two tables for data persistence

Posted by barbgd on Wed, 10 Jun 2020 06:05:02 +0200

Opening notes

  • If you've got inspiration and thinking here, I hope you'll like it! For different opinions on the content, please write and exchange.
  • Technology stack > > java
  • Email > > 15673219519@163.com

describe

  • The system needs to use timed tasks to complete some data processing work.
  • It is hoped that a page can control the execution plan of the task and record the results of each execution. If there is any exception, record it to the database.
  • quartz official website gives 11 tables to work together. In fact, it doesn't need so many functions (maybe your project needs them).
  • I think two kinds of tables can do the daily work_ Job, schedule_ Log (task execution record table).

My thoughts

  • Two types of tables are created to store scheduled task information and execution record information. – step 1
  • It is necessary to record the status results of each execution. During execution, execution failed, execution succeeded, etc. it is impossible to write in each Job class. Originally, it was intended to use aop, but it has not been effective. Later, it was found that it could be implemented through a JobListener listener. It should be noted here that to add a record at the beginning of execution and update the record at the end of execution or exception, in order to ensure that the log ID of the record is obtained, put the log ID in ThreadLocal at the beginning of execution and the log ID obtained from ThreadLocal at the end of execution. – step 3
  • The JobListener listener needs to listen to those tasks, which need to be specified when the JobSchedule is initialized. In the static {} static code block in step 2
  • In order to ensure that the project in the startup state in the database starts normally when the project starts, it is necessary to start the tasks in the database when the project is initialized. – step 4
  • Through page control, I need to provide some interfaces, such as adding a scheduled task, modifying the execution plan / starting or closing, querying the execution record of a task, etc.
  • The operation state of the timed task needs to be updated to the database synchronously, so I invoke the JobSchedule tool class in service to control the task execution, and at the same time the information status of the new data.

Load dependency

  • If it is configured, I use the default. (no writing is the default)
<!--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 1: create a database

  • schedule_job: used to record the information of each scheduled task
CREATE TABLE `schedule_job` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
  `name` varchar(100) NOT NULL COMMENT 'Task name',
  `clazz` varchar(100) NOT NULL COMMENT 'Task execution class',
  `cron` varchar(50) NOT NULL COMMENT 'cron expression',
  `status` int(11) NOT NULL DEFAULT '2' COMMENT '1 On 2 off ',
  `job_group_name` varchar(50) NOT NULL DEFAULT 'DEFAULT_JOB_GROUP',
  `trigger_group_name` varchar(50) NOT NULL DEFAULT 'DEFAULT_TRIGGER_GROUP',
  `des` varchar(200) DEFAULT NULL COMMENT 'describe',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  • schedule_log: used to record the execution details of scheduled tasks
CREATE TABLE `schedule_log` (
  `id` varchar(32) NOT NULL COMMENT 'Primary key',
  `job_id` varchar(100) NOT NULL COMMENT 'Scheduled task id',
  `job_name` varchar(100) DEFAULT NULL,
  `job_clazz` varchar(100) DEFAULT NULL COMMENT 'Task execution class',
  `status` int(11) NOT NULL DEFAULT '1' COMMENT '1 Success 2 exception ',
  `log_info` text COMMENT 'describe',
  `create_time` bigint(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Step 2: create a tool class for scheduled tasks

  • The JobSchedule tool class provides the addition, modification and deletion of tasks.
  • To provide control pages for timed tasks, it is to invoke the tool class in Controller corresponding service to realize the control of timed tasks. (there will be code of Controller - service)
/**
 * @calssName JobSchedule
 * @Description Tool class for task scheduling
 * @Author xxx
 * @DATE 2020/5/28 11:20
 */
public class JobSchedule {
    private static final Logger LOGGER = LoggerFactory.getLogger(JobSchedule.class);
    private static SchedulerFactory gSchedulerFactory = new StdSchedulerFactory();
    private static String JOB_GROUP_NAME = "DEFAULT_JOB_GROUP";
    private static String TRIGGER_GROUP_NAME = "DEFAULT_TRIGGER_GROUP";

    // Bind MyJobListener to job by default_ GROUP_ Name, record and record the execution record of timing task by listening in MyJobListener
    static {
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.getListenerManager().addJobListener(new MyJobListener(),jobGroupEquals(JOB_GROUP_NAME));
        }catch (Exception e){
            LOGGER.error(">>>>>>>>>>>>>>>>>>>>>> Scheduled tasks addJobListener fail >>>>>>>>>>>>>>>>>>>>>>");
        }
    }

    private JobSchedule() {
    }

    /**
     * @Description: Add a scheduled task, use the default task group name, trigger name, trigger group name
     * @param jobName Task name
     * @param cls task
     * @param cron Time setting, refer to the quartz documentation
     * @throws SchedulerException
     *
     */
    public static void addJob(String jobName, Class cls, String cron) throws SchedulerException {

        Scheduler sched = gSchedulerFactory.getScheduler();
        // It is used to describe 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 to specify the rules for triggering
        Trigger trigger = TriggerBuilder.newTrigger()// Create a new trigger builder to standardize a trigger
                .withIdentity(jobName, TRIGGER_GROUP_NAME)// Give trigger a name and group name
                .startNow()// Immediate execution
                .withSchedule(CronScheduleBuilder.cronSchedule(cron)) // Execution time of trigger
                .build();// Generate trigger
        sched.scheduleJob(jobDetail, trigger);
        LOGGER.info("Add task:{},{},{}",jobName,cls,cron);
        // start-up
        if (!sched.isShutdown()) {
            sched.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 Time setting, refer to the quartz documentation
     */
    public static void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class cls, String cron) throws SchedulerException {
        Scheduler sched = gSchedulerFactory.getScheduler();
        // It is used to describe Job implementation class and other static information, and build a Job instance
        JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, jobGroupName).build();
        // Build a trigger to specify the rules for triggering
        Trigger trigger = TriggerBuilder.newTrigger()// Create a new trigger builder to standardize a trigger
                .withIdentity(jobName, triggerGroupName)// Give trigger a name and group name
                .startNow()// Immediate execution
                .withSchedule(CronScheduleBuilder.cronSchedule(cron)) // Execution time of trigger
                .build();// Generate trigger
        sched.scheduleJob(jobDetail, trigger);
        LOGGER.info("Add task:{},{},{},{},{},{}",jobName,jobGroupName,triggerName,triggerGroupName,cls,cron);
        // start-up
        if (!sched.isShutdown()) {
            sched.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 static void modifyJobTime(String jobName, String cron) throws SchedulerException {
        Scheduler sched = gSchedulerFactory.getScheduler();
        TriggerKey triggerKey = new TriggerKey(jobName, TRIGGER_GROUP_NAME);
        CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
        if (trigger == null) {
            return;
        }
        String oldTime = trigger.getCronExpression();
        if (!oldTime.equalsIgnoreCase(cron)) {
            JobDetail jobDetail = sched.getJobDetail(new JobKey(jobName, JOB_GROUP_NAME));
            Class objJobClass = jobDetail.getJobClass();
            removeJob(jobName);
            addJob(jobName, objJobClass, cron);
            LOGGER.info("Modify task:{},{}",jobName,cron);
        }
    }

    /**
     * @Description: Modify or add a task. Modify the task when jobName exists, and add a new task when jobName does not exist
     */
    public static void modifyOrAddJobTime(String jobName, String cron, Class cls) throws SchedulerException {
        Scheduler sched = gSchedulerFactory.getScheduler();
        TriggerKey triggerKey = new TriggerKey(jobName, TRIGGER_GROUP_NAME);
        CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
        if (trigger == null) { // This task does not exist
            addJob(jobName, cls, cron);
            LOGGER.info("Modify task:{},{}",jobName,cron);
        }else{
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                JobDetail jobDetail = sched.getJobDetail(new JobKey(jobName, JOB_GROUP_NAME));
                Class objJobClass = jobDetail.getJobClass();
                removeJob(jobName);
                addJob(jobName, objJobClass, cron);
                LOGGER.info("Modify task:{},{}",jobName,cron);
            }
        }
    }

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

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

    /**
     * Start all tasks
     *
     * @throws SchedulerException
     */
    public static void startJobs() throws SchedulerException {
        Scheduler sched = gSchedulerFactory.getScheduler();
        sched.start();
        LOGGER.info("Start all tasks");
    }

    /**
     * Close all scheduled tasks
     * @throws SchedulerException
     */
    public static void shutdownJobs() throws SchedulerException {
        Scheduler sched = gSchedulerFactory.getScheduler();
        if (!sched.isShutdown()) {
            sched.shutdown();
            LOGGER.info("Close all tasks");
        }
    }
}

Step 3: MyJobListener

  • Scheduled task listener: record and record the execution records of scheduled tasks by listening.
  • When creating a log, put the logId in ThreadLocal. In the execution result, get the logId from ThreadLocal to update the log status.
/**
 * @calssName MyJobListener
 * @Description Timed task listener: record and record the execution records of timed tasks by listening
 * @Author xxx
 * @DATE 2020/5/29 14:18
 */
@Component
public class MyJobListener implements JobListener{
    private JobService jobService;

    // Used to save the logId
    ThreadLocal<String> threadLocal = new ThreadLocal<>();

    @Override
    public String getName() {
        return "myJobListener";
    }
    
    // A new execution log is added at the beginning of execution, and its status is being executed
    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
        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(), "");
        threadLocal.set(logId); // Put the execution record in threadLocal and provide the result after execution
    }
    
    // When the task refuses to execute, the status of the modification log is: execution failed
    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
        String logId = threadLocal.get(); // Execution record logId
        jobService.updateScheduleLog(logId, JobLogStatusEnum.ERROR.getCode(), "Execution failed");
    }
    
    // During task execution, judge whether there is an exception and modify the corresponding status
    @Override
    public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
        String logId = threadLocal.get(); // Execution record logId
        if(e == null){ // No exception modification record as successful
            jobService.updateScheduleLog(logId, JobLogStatusEnum.SUCCESS.getCode(), "Execution successful");
        }else{ // There is an exception modification record failure, and the exception information is saved to the database
            jobService.updateScheduleLog(logId, JobLogStatusEnum.ERROR.getCode(), e.toString());
        }
    }
}

Step 4: make sure the scheduled task is started when restarting the project

  • You need to add the tasks started in the data to the database when the project is initialized
  • This class will execute: xxx implements ServletContextAware after the project is started
@Configuration
public class ScheduleJobInitConfig implements ServletContextAware {
    private static final Logger logger = LoggerFactory.getLogger(ScheduleJobInitConfig.class);
    @Autowired
    private JobService jobService;
    @Override
    public void setServletContext(ServletContext servletContext) {
        jobService.initScheduleJob();
        logger.info("Initialization of scheduled task succeeded ...");
    }
}

Step 5: create a scheduled task

/**
 * @calssName JobA
 * @Description Every time a scheduled task is written, it needs to be scheduled in the database_ A piece of data is configured in the job. When the game admin project is initialized, the started timing tasks in the database will be loaded
 * @Author xxx
 * @DATE 2020/5/28 11:14
 */
@Component
public class JobA implements Job {

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

    @Override
    public void execute(JobExecutionContext context) {
        System.out.println("=================>JobA ");
        // Write the business logic to be executed here
    }
}

The interface method of the control task. The query for adding and deleting tasks is in the following service class

  • To complete the above, the timing task module is basically completed. The next step is to control the interface provided by the interface. So I use postman to call interface control directly.
  • controller
/**
 * @calssName ScheduleController
 * @Description TODO
 * @Author xxx
 * @DATE 2020/5/28 12:26
 */
@RestController
@RequestMapping("/v1/schedule/job")
public class JobController {

    @Autowired
    private JobService jobService;

    /**
     * View all tasks list
     */
    @GetMapping("/list")
    public R getJobList(){
        try{
            Map<String, Object> result = jobService.getAllJob();
            return R.data(result);
        }catch (Exception e){
            return R.error(e);
        }
    }

    /**
     * View execution details of a task
     */
    @GetMapping("/schedule/detail")
    public R getJobScheduleDetail(@RequestParam("jobId") int jobId){
        try{
            Map<String, Object> result = jobService.getJobScheduleDetail(jobId);
            return R.data(result);
        }catch (Exception e){
            return R.error(e);
        }
    }

    /**
     * View details of a task
     */
    @GetMapping("/detail")
    public R getJobDetail(@RequestParam("jobId") int jobId){
        try{
            Map<String, Object> result = jobService.getJobDetail(jobId);
            return R.data(result);
        }catch (Exception e){
            return R.error(e);
        }
    }

    /**
     * Modify task
     */
    @PostMapping("/update")
    public R updateJob(ScheduleJob job){
        try{
            Map<String, Object> result = jobService.updateJob(job);
            return R.data(result);
        }catch (Exception e){
            return R.error(e);
        }
    }
}
  • service
/**
 * @calssName JobService
 * @Description
 * @Author xxx
 * @DATE 2020/5/28 15:59
 */
@Service
public class JobService {

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

    @Autowired
    private ScheduleJobRepoImpl scheduleJobRepo;
    @Autowired
    private ScheduleLogRepoImpl scheduleLogRepo;

    /**
     * View all tasks list
     */
    public Map<String,Object> getAllJob(){
        List<ScheduleJob> list = scheduleJobRepo.getList();
        Map<String,Object> result = new HashMap<>();
        result.put("list", list);
        return result;
    }


    /**
     * View execution details of a task
     */
    public Map<String,Object> getJobScheduleDetail(int jobId){
        List<ScheduleLog> list = scheduleLogRepo.getListByJobId(jobId);
        Map<String,Object> result = new HashMap<>();
        result.put("list", list);
        return result;
    }

    /**
     * View details of a task
     */
    public Map<String,Object> getJobDetail(int jobId){
        ScheduleJob scheduleJob = scheduleJobRepo.getScheduleJobById(jobId);
        Map<String,Object> result = new HashMap<>();
        result.put("scheduleJob", scheduleJob);
        return result;
    }

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

        Class<?> clszz = null;
        try {
            clszz = Class.forName(scheduleJob.getClazz());
        }catch (ClassNotFoundException e){
            logger.error("The specified task class cannot be found due to the exception of modifying the scheduled task");
            throw new AppException(ErrorCode.SYS_PARAMS_ERROR.code(), "Unable to find the specified task class");
        }

        try {
            Integer status = scheduleJob.getStatus();
            if(status == 1){ // Modify or add tasks
                JobSchedule.modifyOrAddJobTime(scheduleJob.getName(), scheduleJob.getCron(),clszz);
            }else if(status == 2){ // Stop task
                JobSchedule.removeJob(scheduleJob.getName());
            }else{
                return result;
            }
        }catch (SchedulerException e){
            logger.error("Exception in modifying scheduled task:{}", e);
            throw new AppException(ErrorCode.SYS_ERROR);
        }

        scheduleJobRepo.updateScheduleJob(scheduleJob);
        return result;
    }

    /**
     * Start scheduled tasks configured in the database
     */
    public void initScheduleJob(){
        List<ScheduleJob> list = scheduleJobRepo.getEnableList();

        for (ScheduleJob scheduleJob: list){
            Class<?> clszz = null;
            try {
                clszz = Class.forName(scheduleJob.getClazz());
                JobSchedule.addJob(scheduleJob.getName(), clszz, scheduleJob.getCron());
            } catch (SchedulerException e){
                logger.error("Initialization start timing task exception:{}", e);
                continue;
            } catch (ClassNotFoundException e){
                logger.error("Initialization start timer task exception, unable to find the specified task class");
                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){
        scheduleLogRepo.updateScheduleLog(id,status,logInfo);
    }
}

last

  • If you've got inspiration and thinking here, I hope you'll like it! For different opinions on the content, please write and exchange.
  • Technology stack > > java
  • Email > > 15673219519@163.com

Topics: Database Java Spring