Quartz task scheduling (detailed)

Quartz task scheduling

1, The concept of Quartz

1. Basic introduction

Quartz is another open source project of OpenSymphony in the field of Job scheduling. It can be combined with J2EE and J2SE applications or used alone.

Quartz is an open source "task scheduling library" with rich features, which can be integrated into any Java application, from independent application to e-commerce system. Quartz can create simple and complex schedules to perform tens, hundreds, even tens of thousands of tasks. Task job is defined as a standard Java component, which can perform any function you want. Quartz scheduling framework includes many enterprise level features, such as JTA transaction and cluster support.

In short, Quartz is a Java based task scheduling framework for any task you want to perform.

Official website: http://www.quartz-scheduler.org/
Official document: http://www.quartz-scheduler.org/documentation/
Original address: https://github.com/quartz-scheduler/quartz

2. Quartz running environment

  • Quartz can run embedded in another stand-alone application
  • Quartz can be instantiated in an application server (or Servlet container) and participate in transactions
  • Quartz can be run as a stand-alone program (in its own Java virtual machine) and can be used through RMI
  • Quartz can be instantiated as an independent project cluster (load balancing and fail over functions) for job execution

3. The core concept of quartz

  • Task Job

    Job is the task class you want to implement. Every job must implement the org.quartz.job interface, and only need to implement the execute() method defined by the interface.

  • Trigger trigger

    Trigger for you to perform tasks. For example, if you want to send a statistical email at 3 o'clock every day, trigger will set 3 o'clock to perform the task.
    There are two kinds of Trigger: SimplerTrigger and CronTrigger. See 7.9 and 7.10 for details

  • Scheduler

    The Scheduler is the Scheduler of the task, which integrates the task Job and Trigger trigger, and is responsible for executing the Job based on the time set by Trigger.

4. Architecture of quartz

2, The use of Quart

1. Introducing jar package of Quartz

Create a springboot (version: 2.2.4.RELEASE) application and introduce the dependency directly

<dependencies>
	<!-- Quartz Core package -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
    </dependency>

	<!-- Quartz tool kit -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz-jobs</artifactId>
    </dependency>
</dependencies>

2. Introduction case

(1) Create HelloJob task class

// Define task class
public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        // Output current time
        ystem.out.println(new Date());
    }
}

(2) Creating a task scheduling class HelloSchedulerDemo

public class HelloSchedulerDemo {

    public static void main(String[] args) throws Exception {
        // 1. Scheduler to obtain the instance of the schedule from the factory (default: instantiate new StdSchedulerFactory();)
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // 2. The Job detail defines a task scheduling instance, which is bound to HelloJob. The task class needs to implement the Job interface
        JobDetail jobDetail = JobBuilder.newJob() // Load the task class, complete the binding with HelloJob, and require HelloJob to implement the Job interface
                .withIdentity("job1", "group1") // Parameter 1: the name of the task (unique instance); Parameter 2: the name of the task group
                .build();

        // 3. Trigger defines a trigger, executes immediately, and then repeats every 5 seconds
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1") // Parameter 1: name of trigger (unique instance); Parameter 2: the name of the trigger group
                .startNow() // Trigger now
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()) // Repeat every 2 seconds
                .build();

        // 4. Let the scheduler associate tasks and triggers to ensure that tasks are executed according to the adjustment defined by triggers
        scheduler.scheduleJob(jobDetail, trigger);

        // 5. Start up
        scheduler.start();
        // close
        //scheduler.shutdown();
    }

}

3. Job and JobDetail

  • Job: the interface of work task scheduling, which the task needs to implement. This interface defines the execute method, which is similar to the run method of the TimeTask class provided by JDK. Write the business logic of task execution in it.
  • Life cycle of Job instance in Quartz: every time the scheduler executes a Job, it will create a new Job instance before calling the execute method. When the call is completed, the associated Job object instance will be released, and the released instance will be recycled by the garbage collection mechanism.
  • JobDetail: JobDetail provides many setting properties for Job instances, as well as the member variable properties of JobDataMap, which are used to store the status information of specific Job instances. The scheduler needs to add Job instances with the help of the JobDetail object.
  • Important properties of JobDetail: name, group, jobClass, JobDataMap
JobDetail job = JobBuilder.newJob(HelloJob.class)
        .withIdentity("job1", "group1") // Define the unique identity of the instance and specify a group
        .build();

System.out.println("name:" +job.getKey().getName());
System.out.println("group:" +job.getKey().getGroup());
System.out.println("jobClass:" +job.getJobClass().getName());

4,JobExecutionContext

  • When the Scheduler calls a Job, it will pass the JobExecutionContext to the execute() method of the Job;
  • Job can access the environment of Quartz runtime and the detailed data of job itself through JobExecutionContext object.
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        Trigger trigger = jobExecutionContext.getTrigger(); //Get Trigger
        JobDetail jobDetail = jobExecutionContext.getJobDetail(); //Get JobDetail
        Scheduler scheduler = jobExecutionContext.getScheduler(); //Get Scheduler

        trigger.getKey().getName(); //Get Trigger name
        trigger.getKey().getGroup(); //Get Trigger group name (DEFAULT)

        jobExecutionContext.getScheduledFireTime(); //The scheduled time when the trigger is triggered.
        jobExecutionContext.getFireTime(); //Actual trigger time. For example, the scheduled time may be 10:00:00, but if the scheduler is too busy, the actual trigger time may be 10:00:03.
        jobExecutionContext.getPreviousFireTime(); //Last trigger time
        jobExecutionContext.getNextFireTime(); //Next trigger time

        System.out.println(new Date());
    }
}

5. Introduction to JobDataMap

(1) Using Map to get

  • When scheduling tasks, the JobDataMap is stored in the JobExecutionContext, which is very convenient to obtain.
  • JobDataMap can be used to load any serializable data object, and these parameter objects will be passed to the Job instance object when it is executed.
  • JobDataMap implements the Map interface of JDK, and adds a very convenient method to access the basic data types.

When defining a Trigger or Job Detail, pass in the JobDataMap, and then you can get the parameters in the JobDataMap

public class HelloScheduler {
    public static void main(String[] args) throws SchedulerException {
        //1. Scheduler
        Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
		
		JobDataMap jobDataMap2 = new JobDataMap();
        jobDataMap2.put("message", "JobDetailMessage");

        //2. Task instance (JobDetail)
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "jobGroup1")
                .usingJobData(jobDataMap2)
                .build();

		// Define JobDataMap
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("message", "TriggerMessage");

        //3. Trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "triggerGroup1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
                .endAt(new Date(new Date().getTime() + 3000L))
                .usingJobData(jobDataMap) // Put JobDataMap into Trigger
                .build();

        defaultScheduler.scheduleJob(jobDetail, trigger);
        defaultScheduler.start();
    }
}

HelloJob.java

public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    	System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("message")); //TriggerMessage
        System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("message")); //JobDetailMessage

        System.out.println(jobExecutionContext.getMergedJobDataMap().get("message")); //TriggerMessage
        System.out.println(new Date());
    }
}

(2) Using the Setter method to get

The key value of the Job datamap corresponding to the setter method is added to the Job implementation class. The default JobFactory implementation class of the Quartz framework will automatically call these setter methods when initializing the Job instance object.

The helloschedule class is the same as above.

HelloJob.java:

@Data
public class HelloJob implements Job {

    private String message;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(message); //TriggerMessage
        System.out.println(new Date());
    }
}

Note: if a key with the same name is encountered, the value of JobDataMap in Trigger will override the key with the same name in JobDetail

6. Stateful jobs and stateless jobs (@ PersistJobDataAfterExecution)

A stateful Job can be understood as holding some state information during multiple Job calls, which is stored in the JobDataMap, while the default stateless Job creates a new JobDataMap every time it is called.

(1) Modify HelloSchedulerDemo.java. Add. usingJobData("count", 0) to JobDetail to indicate the counter.

JobDetail job = JobBuilder.newJob(HelloJob.class)
        .withIdentity("job1", "group1") // Define the unique identity of the instance and specify a group
        .usingJobData("message", "Print log")
        .usingJobData("count", 0)
        .build();

(2)HelloJob.java

@Data
@PersistJobDataAfterExecution
public class HelloJob implements Job {

    private Integer count;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(++count);
        jobExecutionContext.getJobDetail().getJobDataMap().put("count", count);
    }
}

HelloJob class does not add @ PersistJobDataAfterExecution annotation. A new JobDataMap is created every time it is called. It doesn't add up.

HelloJob class adds @ PersistJobDataAfterExecution annotation, which can hold some state information during multiple calls, that is, count accumulation can be realized.

7,Trigger

(1) SimpleTrigger trigger

SimpleTrigger is the simplest quartztigger for setting and using.

It is designed for jobs that need to be started at a specific date / time and repeated n times at a possible interval.

Case 1: it means to execute a task within a specified period of time;

Common methods of SimpleTrigger are as follows:

methodexplain
startNow()When the Scheduler starts executing, the trigger executes
startAt(new Date())Start execution at the specified time
withIntervalInSeconds(2)Execution interval, corresponding time unit in method name
repeatForever()Repeat it all the time
withRepeatCount(3)Repeat the specified number of times
endAt(new Date())End time

example:

//Start execution immediately, once every 2 seconds, repeat 3 times, and end execution after 3 seconds (when one of the repeat times or end time reaches first, execution will stop)
Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "triggerGroup1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).withRepeatCount(3))
                .endAt(new Date(new Date().getTime() + 3000L))
                .build();

Points to pay attention to

  • The properties of SimpleTrigger are: start time, end time, repeat times and repeat interval.
  • The value of the repeat property can be 0, a positive integer, or the constant SimpleTrigger.REPEAT_INDEFINITELY.
  • The repeated time interval attribute value must be a positive integer greater than 0 or long shaping, with milliseconds as the time unit. When the repeated time interval is 0, it means triggering execution with Trigger at the same time.
  • If there is a specified end time attribute value, the end time attribute takes precedence over the number of repetitions attribute. The advantage is that when we need to create a Trigger that triggers every 10 seconds until the specified end time, instead of calculating the number of repetitions from the beginning to the end, we just need to specify the end time and use REPEAT_INDEFINITELY can be used as the attribute value of the number of repetitions.

(2) CronTrigger trigger

If you need to trigger tasks on schedule like a calendar, rather than at specific intervals like SimpleTrigger, CronTrigger is usually more useful than SimpleTrigger because it is a calendar based job scheduler.

Case study:

Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("trigger1", "group1")
        .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))  // calendar
        .build();

8, SchedulerFactory

Quartz is a modular architecture, so to make it work, several components must be well integrated. Fortunately, there are some existing assistants who can do the work.

All Scheduler instances are created by SchedulerFactory.

There are three core concepts of Quartz: scheduler, task and trigger

As we all know, the three most important elements of a Job are Scheduler, JobDetail and trigger; Trigger is like a driver for a Job. If there is no trigger to drive the Job regularly, the Job cannot run; For a Job, a Job can correspond to multiple triggers, but for a trigger, a trigger can only correspond to one Job, so a trigger can only be assigned to one Job; If you need a more responsible trigger plan, you can create multiple triggers and assign them to the same Job.

(1)StdSchedulerFactory

The default SchedulerFactory of Quartz

  • Use a set of parameters (java.util.Properties) to create and initialize the Quartz scheduler
  • Configuration parameters are generally stored in the quartz.properties file
  • Calling the getScheduler method creates and initializes the scheduler object

Creation method:

//Static method
Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();

//Example method
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = stdSchedulerFactory.getScheduler();

Common methods:

scheduler.scheduleJob(jobDetail, trigger); //Binding jobDetail and trigger
scheduler.start();		//Start task scheduling
scheduler.pauseJob();	//Task scheduling is suspended, that is, operation is suspended
scheduler.standby();	//Task scheduling is suspended, that is, operation is suspended
scheduler.shutdown();	//Close task scheduling, the same as shutdown(false)
scheduler.shutdown(true);	//It means to wait for all jobs to be executed before closing the Scheduler
scheduler.shutdown(false);	// Indicates to close the Scheduler directly

(2) DirectSchedulerFactory (understanding)

DirectSchedulerFactory is a direct implementation of SchedulerFactory, through which you can directly build schedulers, threadpools, etc

DirectSchedulerFactory directSchedulerFactory = DirectSchedulerFactory.getInstance();
Scheduler scheduler = directSchedulerFactory.getScheduler();

9, Quartz.properties

Default path: org.quartz.quartz.properties in quartz-2.3.2

We can also add the quartz.properties file under the resources of the project to cover the underlying configuration file.

#===============================================================
#Configure Main Scheduler Properties scheduler properties
#===============================================================
#The instance name of the scheduler
org.quartz.scheduler.instanceName = QuartzScheduler
#The instance ID of the scheduler can be set to AUTO in most cases
org.quartz.scheduler.instanceId = AUTO

#===============================================================
#Configure ThreadPool thread pool properties
#===============================================================
#The number of threads to process jobs should be at least 1, but it's better not to exceed 100 at most. It's quite impractical to set the value above 100 on most machines, especially when your Job takes a long time to execute
org.quartz.threadPool.threadCount =  5
#The priority of a thread. A thread with a higher priority has priority over a thread with a lower priority. The minimum is 1, the maximum is 10, and the default is 5
org.quartz.threadPool.threadPriority = 5
#A class that implements the org.quartz.spi.threadPool interface. The thread pool implementation class of Quartz is org.Quartz.simplethreadpool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

#===============================================================
#Configure JobStore job storage settings
#===============================================================
#Job s are stored in memory by default, which is the following configuration
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

#===============================================================
#Configure Plugins plug in configuration
#===============================================================
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin

org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.validating=false

You can also write program code to operate the contents of the quartz.properties file

// Create factory instance
StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
// Create an object that configures the properties of the factory
Properties prop = new Properties();
prop.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "5");

try {
    // Load the properties defined above
    schedulerFactory.initialize(prop);

    Scheduler scheduler = schedulerFactory.getScheduler();

    scheduler.start();
} catch (SchedulerException e) {
    e.printStackTrace();
}

3, Quartz monitor

1. Concept

The listener of quartz is used to get the notification of the event in time when the event you pay attention to occurs in the task scheduling. It is similar to the reminder of email and SMS in the process of task execution. Quartz listeners are mainly composed of JobListener, TriggerListener and SchedulerListener. As the name suggests, they are distributed to represent the listeners corresponding to tasks, triggers and schedulers. Before introducing the three kinds of listeners, we need to make clear two concepts: Global listener and non global listener

  • The global listener can receive all Job/Trigger event notifications
  • The non global listener can only receive the events of the Job or Trigger registered on it, and the Job or Trigger not registered on it will not listen.

This course will introduce the use of global and non global listeners one by one.

2,JobListener

In the process of task scheduling, the events related to job include: the prompt of job start to execute; Job execution completion prompt, etc.

public interface JobListener {
    public String getName();
    public void jobToBeExecuted(JobExecutionContext context);
    public void jobExecutionVetoed(JobExecutionContext context);
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException);
}

Among them:

. getName method: used to get the name of the JobListener.
. jobToBeExecuted method: the Scheduler calls this method when JobDetail is about to be executed.
. jobExecutionVetoed method: the Scheduler will call this method when the JobDetail is about to be executed, but it is rejected by the TriggerListener.
jobWasExecuted method: Scheduler calls this method after JobDetail is executed.

Example:

HelloJobListener.java

// Define task class
public class HelloJobListener implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // Output current time
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateString = dateFormat.format(date);
        // job content
        System.out.println("The database is being backed up at the following time:" +dateString);
    }
}

Create a custom JobListener

MyJobListener.java

public class MyJobListener implements JobListener {

    @Override
    public String getName() {
        String name = this.getClass().getSimpleName();
        System.out.println("The name of the listener is:" +name);
        return name;
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        String name = context.getJobDetail().getKey().getName();
        System.out.println("Job The name of the project is:" +name + "          Scheduler stay JobDetail The method to be called when it is about to be executed");
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        String name = context.getJobDetail().getKey().getName();
        System.out.println("Job The name of the project is:" +name + "          Scheduler stay JobDetail To be executed, but to be executed TriggerListener The method is called when it is vetoed");
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        String name = context.getJobDetail().getKey().getName();
        System.out.println("Job The name of the project is:" +name + "          Scheduler stay JobDetail This method is called after execution.");
    }

}

Execution scheduler

HelloSchedulerDemoJobListener.java

public class HelloSchedulerDemoJobListener {

    public static void main(String[] args) throws Exception {
        // 1. Scheduler to obtain the instance of the schedule from the factory (default: instantiate new StdSchedulerFactory();)
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // 2. The Job detail defines a task scheduling instance, which is bound to hellojob simpletrigger. The Job class needs to implement the Job interface
        JobDetail jobDetail = JobBuilder.newJob(HelloJobListener.class) // Load the task class, complete the binding with HelloJob, and require HelloJob to implement the Job interface
                .withIdentity("job1", "group1") // Parameter 1: the name of the task (unique instance); Parameter 2: the name of the task group
                .build();

        // 3. Trigger defines a trigger, executes immediately, and then repeats every 5 seconds
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1") // Parameter 1: name of trigger (unique instance); Parameter 2: the name of the trigger group
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5).withRepeatCount(2))  // It is executed every 5 seconds and stops after 3 consecutive times. The default value is 0
                .build();
        // 4. Let the scheduler associate tasks and triggers to ensure that tasks are executed according to the adjustment defined by triggers
        scheduler.scheduleJob(jobDetail, trigger);
        
        // Create and register a global Job Listener
        // scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
        // Create and register a local Job Listener to represent the specified Job
        scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("job1", "group1")));

        // 5. Start up
        scheduler.start();
        // close
        //scheduler.shutdown();
    }

}

3,TriggerListener

In the process of task scheduling, Trigger trigger related events include: Trigger triggered, Trigger not triggered correctly, Trigger completed, etc.

public interface TriggerListener {
    public String getName();
    public void triggerFired(Trigger trigger, JobExecutionContext context);
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
    public void triggerMisfired(Trigger trigger);
    public void triggerComplete(Trigger trigger, JobExecutionContext context,            CompletedExecutionInstruction triggerInstructionCode)
}

Among them:

. getName method: used to get the name of the trigger.
. triggerFired method: when the Trigger associated with the listener is triggered and the Execute() method on the Job will be executed, the Scheduler calls this method.
. vetojoboxecution method: after Trigger, the Scheduler calls this method when the Job is about to be executed. TriggerListener gives you an option to veto Job execution. If this method returns true, the Job will not be executed for this Trigger.
. Trigger misfire method: the Scheduler calls this method when the Trigger misses the Trigger. You should pay attention to the long-lasting logic in this method: when there are many triggers that miss the Trigger, the long logic will lead to the domino effect. You should keep this method as small as possible.
. triggerComplete method: when Trigger is triggered and Job execution is completed, the Scheduler calls this method.

Example:

The following example briefly shows the use of TriggerListener, where creating and registering a TriggerListener is almost similar to a JobListener.

HelloJobListener.java

// Define task class
public class HelloJobListener implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // Output current time
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateString = dateFormat.format(date);
        // job content
        System.out.println("The database is being backed up at the following time:" +dateString);
    }
}

MyTriggerListener.java

public class MyTriggerListener implements TriggerListener {
    
    private String name;
    // Construct a method to customize the name of the pass trigger. The default is the name of the class
    public MyTriggerListener(String name) {
        super();
        this.name = name;
    }
    @Override
    public String getName() {
        return this.name;  // No return will throw an exception with a null name
    }

//    @Override
//    public String getName() {
//        String name = this.getClass().getSimpleName();
//        System.out.println("trigger name: + name)";
//        return name;  //  No return will throw an exception with a null name
//    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        String name = this.getClass().getSimpleName();
        System.out.println(name +"Triggered");
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        String name = this.getClass().getSimpleName();
        // TriggerListener gives you an option to veto Job execution. If this method returns true, the Job will not be executed for this Trigger.
        System.out.println(name +" It's not triggered");
        return false;  // true: indicates that the Job method will not be executed
    }

    @Override
    public void triggerMisfired(Trigger trigger) {
        String name = this.getClass().getSimpleName();
        // The Scheduler calls this method when Trigger is missed
        System.out.println(name +" Miss trigger");
    }

    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode) {
        String name = this.getClass().getSimpleName();
        // When Trigger is triggered and Job execution is completed, the Scheduler calls this method.
        System.out.println(name +" Trigger after completion");
    }

}

HelloSchedulerDemoTriggerListener.java

public class HelloSchedulerDemoTriggerListener {

    public static void main(String[] args) throws Exception {
        // 1. Scheduler to obtain the instance of the schedule from the factory (default: instantiate new StdSchedulerFactory();)
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // 2. The Job detail defines a task scheduling instance, which is bound to hellojob simpletrigger. The Job class needs to implement the Job interface
        JobDetail jobDetail = JobBuilder.newJob(HelloJobListener.class) // Load the task class, complete the binding with HelloJob, and require HelloJob to implement the Job interface
                .withIdentity("job1", "group1") // Parameter 1: the name of the task (unique instance); Parameter 2: the name of the task group
                .build();

        // 3. Trigger defines a trigger, executes immediately, and then repeats every 5 seconds
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1") // Parameter 1: name of trigger (unique instance); Parameter 2: the name of the trigger group
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5).withRepeatCount(2))  // It is executed every 5 seconds and stops after 3 consecutive times. The default value is 0
                .build();
        // 4. Let the scheduler associate tasks and triggers to ensure that tasks are executed according to the adjustment defined by triggers
        scheduler.scheduleJob(jobDetail, trigger);

        // Create and register a global Trigger Listener
        // scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), EverythingMatcher.allTriggers());
        // Create and register a local Trigger Listener
        scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "group1")));

        // 5. Start up
        scheduler.start();
        // close
        //scheduler.shutdown();
    }

}

4,SchedulerListener

SchedulerListener is called when key events occur in the life cycle of the Scheduler. Events related to Scheduler include: adding a Job/Trigger, deleting a Job/Trigger, serious error in Scheduler, closing Scheduler, etc.

public interface SchedulerListener {
    public void jobScheduled(Trigger trigger);
    public void jobUnscheduled(TriggerKey triggerKey);
    public void triggerFinalized(Trigger trigger);
    public void triggersPaused(String triggerGroup);
    public void triggersResumed(String triggerGroup);
    public void jobsPaused(String jobGroup);
    public void jobsResumed(String jobGroup);
    public void schedulerError(String msg, SchedulerException cause);
    public void schedulerStarted();
    public void schedulerInStandbyMode();
    public void schedulerShutdown();
    public void schedulingDataCleared()
}

Among them:

. jobScheduled method: used to deploy JobDetail.
. jobUnscheduled method: used to unload JobDetail.
. triggerFinalized method: this method is called when a Trigger comes to a state that will never be triggered again. Unless the Job is persistent, it will be removed from the Scheduler.
. triggersPaused method: the Scheduler calls this method when a Trigger or Trigger group is suspended. If it is a Trigger group, the Trigger name parameter will be null.
. triggersResumed method: the Scheduler calls this method when a Trigger or Trigger group resumes from a pause. If it is a Trigger group, the Trigger name parameter will be null.
. jobsPaused method: this method is called when one or a group of jobdetails is suspended.
. jobsResumed method: this method is called when one or a group of jobs resume from the pause. If it is a Job group, the jobName will be null.
. schedulerError method: this method is called when a serious error occurs during the normal operation of the Scheduler.
. schedulerStarted method: this method is called when the Scheduler is on.
. schedulerInStandbyMode method: this method is called when the Scheduler is in standbymode.
. schedulerShutdown method: this method is called when the Scheduler stops.
. schedulingDataCleared method: this method is called when the data in the Scheduler is cleared.

Example:

The following code briefly describes how to use the SchedulerListener method:

HelloJobListener.java

// Define task class
public class HelloJobListener implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // Output current time
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateString = dateFormat.format(date);
        // job content
        System.out.println("The database is being backed up at the following time:" +dateString);
    }
}

MySchedulerListener.java

public class MySchedulerListener implements SchedulerListener {

    @Override
    public void jobScheduled(Trigger trigger) {
        String name = trigger.getKey().getName();
        // Used to deploy JobDetail
        System.out.println(name +" Complete the deployment");
    }

    @Override
    public void jobUnscheduled(TriggerKey triggerKey) {
        String name = triggerKey.getName();
        // Used to unload JobDetail
        System.out.println(name +" Complete the uninstall");
    }

    @Override
    public void triggerFinalized(Trigger trigger) {
        String name = trigger.getKey().getName();
        // This method is called when a Trigger comes to a state that will never be triggered again. Unless the Job is persistent, it will be removed from the Scheduler.
        System.out.println(name +" Trigger removed");
    }

    @Override
    public void triggerPaused(TriggerKey triggerKey) {
        String name = triggerKey.getName();
        // The Scheduler calls this method when a Trigger or Trigger group is suspended. If it is a Trigger group, the Trigger name parameter will be null.
        System.out.println(name +" Being suspended");
    }

    @Override
    public void triggersPaused(String triggerGroup) {
        // The Scheduler calls this method when a Trigger or Trigger group is suspended. If it is a Trigger group, the Trigger name parameter will be null.
        System.out.println("Trigger group" +triggerGroup +" Being suspended");
    }

    @Override
    public void triggerResumed(TriggerKey triggerKey) {
        // The Scheduler calls this method when a Trigger or Trigger group resumes from a pause. If it is a Trigger group, the Trigger name parameter will be null. The parameter will be null.
        String name = triggerKey.getName();
        System.out.println(name +" Resuming from pause");
    }

    @Override
    public void triggersResumed(String triggerGroup) {
        // The Scheduler calls this method when a Trigger or Trigger group resumes from a pause. If it is a Trigger group, the Trigger name parameter will be null. The parameter will be null.
        System.out.println("Trigger group" +triggerGroup +" Resuming from pause");
    }

    @Override
    public void jobAdded(JobDetail jobDetail) {
        // 
        System.out.println(jobDetail.getKey() +" Add task");
    }

    @Override
    public void jobDeleted(JobKey jobKey) {
        // 
        System.out.println(jobKey +" Delete work task");
    }

    @Override
    public void jobPaused(JobKey jobKey) {
        // 
        System.out.println(jobKey +" The task is being suspended");
    }

    @Override
    public void jobsPaused(String jobGroup) {
        // 
        System.out.println("Working group" +jobGroup +" Being suspended");
    }

    @Override
    public void jobResumed(JobKey jobKey) {
        // 
        System.out.println(jobKey +" Resuming from pause");
    }

    @Override
    public void jobsResumed(String jobGroup) {
        // 
        System.out.println("Working group" +jobGroup +" Resuming from pause");
    }

    @Override
    public void schedulerError(String msg, SchedulerException cause) {
        // This method is called when a serious error occurs during the normal operation of the Scheduler.
        System.out.println("Called when a serious error occurs" +msg +"    " +cause.getUnderlyingException());
    }

    @Override
    public void schedulerInStandbyMode() {
        // This method is called when the Scheduler is in StandBy mode.
        System.out.println("Called when the scheduler is suspended");
    }

    @Override
    public void schedulerStarted() {
        // This method is called when the Scheduler is on
        System.out.println("Called when the scheduler is on");
    }

    @Override
    public void schedulerStarting() {
        // 
        System.out.println("Called when the scheduler is turning on");
    }

    @Override
    public void schedulerShutdown() {
        // 
        System.out.println("Called when the scheduler is closed");
    }

    @Override
    public void schedulerShuttingdown() {
        // 
        System.out.println("Called when the scheduler is shutting down");
    }

    @Override
    public void schedulingDataCleared() {
        // This method is called when the data in the Scheduler is cleared
        System.out.println("Called when scheduler data is cleared");
    }

}

HelloSchedulerDemoTriggerListener.java

public class HelloSchedulerDemoTriggerListener {

    public static void main(String[] args) throws Exception {
        // 1. Scheduler to obtain the instance of the schedule from the factory (default: instantiate new StdSchedulerFactory();)
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // 2. The Job detail defines a task scheduling instance, which is bound to hellojob simpletrigger. The Job class needs to implement the Job interface
        JobDetail jobDetail = JobBuilder.newJob(HelloJobListener.class) // Load the task class, complete the binding with HelloJob, and require HelloJob to implement the Job interface
                .withIdentity("job1", "group1") // Parameter 1: the name of the task (unique instance); Parameter 2: the name of the task group
                .build();

        // 3. Trigger defines a trigger, executes immediately, and then repeats every 5 seconds
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1") // Parameter 1: name of trigger (unique instance); Parameter 2: the name of the trigger group
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5).withRepeatCount(2))  // It is executed every 5 seconds and stops after 3 consecutive times. The default value is 0
                .build();
        // 4. Let the scheduler associate tasks and triggers to ensure that tasks are executed according to the adjustment defined by triggers
        scheduler.scheduleJob(jobDetail, trigger);

        // Creating a listener for the scheduler
        scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());
        // Remove the listener of the corresponding scheduler
        // scheduler.getListenerManager().removeSchedulerListener(new MySchedulerListener());

        // 5. Start up
        scheduler.start();

        // Thread shut down after 7 seconds delay
        Thread.sleep(7000L);

        // close
        scheduler.shutdown();
    }

}

4, Persistent to Mysql

1. Download sql file

There are sql files in the original code of Quartz

Download and import it into the database. I use mysql5.7 here

Table namedescribe
QRTZ_BLOB_TRIGGERSStore as Blob type (used when Quartz users create their own custom Trigger type with JDBC, and JobStore doesn't know how to store instances)
QRTZ_CALENDARSStore Calendar information of Quartz in Blob type
QRTZ_CRON_TRIGGERSStore Cron Trigger, including Cron expression and time zone information
QRTZ_FIRED_TRIGGERSStore status information related to triggered Trigger and execution information of associated Job
QRTZ_JOB_DETAILSStore the details of each configured Job
QRTZ_LOCKSStore the program's non watch lock information (if pessimistic lock is used)
QRTZ_PAUSED_TRIGGER_GRPSStores information about the pending Trigger group
QRTZ_SCHEDULER_STATEStore a small amount of state information about the Scheduler, and other instances of the Scheduler (if used in a cluster)
QRTZ_SIMPLE_TRIGGERSStore a simple Trigger, including the number of repetitions, intervals, and the number of touches
QRTZ_SIMPROP_TRIGGERS
QRTZ_TRIGGERSStores information about the configured Trigger

2. Introduce dependency

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zyx</groupId>
<artifactId>quartz</artifactId>
<version>0.0.1-SNAPSHOT</version>

<properties>
    <java.version>1.8</java.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <exclusions>
            <exclusion>
            	<!--Exclude the self-contained JDBC Connection pool-->
                <groupId>com.mchange</groupId>
                <artifactId>c3p0</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz-jobs</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

	<!--Timed tasks need to rely on context modular-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
    </dependency>

    <!--Connect to database-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--The following package can be replaced with mybatis perhaps mybatisPlus-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
</dependencies>

3. Configure SchedulerFactory

To configure a data source:

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://10.211.55.12:3306/test
    driver-class-name: com.mysql.cj.jdbc.Driver

To configure SchedulerFactory:

@Configuration
public class ScheduleConfig {

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
    {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);

        // quartz parameter
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "ZyxScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO"); //If cluster is used, instanceId must be unique and set to AUTO
        // Thread pool configuration
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "20"); //Number of threads
        prop.put("org.quartz.threadPool.threadPriority", "5"); //priority
        // JobStore configuration
        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX"); //Configure usage database
        // Cluster configuration
        prop.put("org.quartz.jobStore.isClustered", "true"); //Is it cluster mode
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");

        // Enable SQL Server
        // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); //Database table prefix
        factory.setQuartzProperties(prop);

        factory.setSchedulerName("DesScheduler");
        // Delayed start
        factory.setStartupDelay(1);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        // Optional, QuartzScheduler
        // Update existing jobs at startup, so that you don't need to delete qrtz after modifying targetObject every time_ Job_ The details table corresponds to the record
        factory.setOverwriteExistingJobs(true);
        // Set auto start, the default is true
        factory.setAutoStartup(true);

        return factory;
    }
}

4. Use custom Scheduler

A simple Job:

@Data
public class HelloJob implements Job {
    
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(new Date());
    }
}

Use a custom Scheduler:

@SpringBootTest
class QuartzApplicationTests {

	//Inject the above configured factoryBean
    @Autowired
    private SchedulerFactoryBean factoryBean;

    @Test
    void contextLoads() throws SchedulerException, InterruptedException {
        Scheduler scheduler = factoryBean.getScheduler();
        scheduler.clear();

        //2. Task instance (JobDetail)
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "jobGroup1")
                .build();

        //3. Trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .startNow()
                .withIdentity("trigger1", "triggerGroup1")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
                .build();

        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
        Thread.sleep(100000);
    }
}

5. View the database

Looking at the database, you can find that the relevant data in Quartz has been saved to the database

6. Start the Scheduler again

Let the program run directly, without creating a new timing task, you will find that the timing task just saved in the database will be executed automatically

@SpringBootTest
class QuartzApplicationTests {

    @Autowired
    private SchedulerFactoryBean factoryBean;

    @Test
    void contextLoads() throws SchedulerException, InterruptedException {
        Thread.sleep(100000);
    }

}
#Use your own profile
org.quartz.jobStore.useProperties=true

#Default or change your name
org.quartz.scheduler.instanceName= DefaultQuartzScheduler
#If cluster is used, instanceId must be unique and set to AUTO
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


#The storage method uses JobStoreTX, that is, database
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#Whether to use cluster (if the project is deployed to only one server, it is unnecessary)
org.quartz.jobStore.isClustered = false
org.quartz.jobStore.clusterCheckinInterval=20000
org.quartz.jobStore.tablePrefix = qrtz_ 
org.quartz.jobStore.dataSource = test

#Configure data sources
#Table name prefix of quartz table in database
org.quartz.dataSource.test.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.test.URL = jdbc:mysql://localhost:3306/test?serverTimezone=GMT&characterEncoding=utf-8
org.quartz.dataSource.test.user = root
org.quartz.dataSource.test.password = root
org.quartz.dataSource.test.maxConnections = 5

Tags: MySQL Distribution Quartz

Posted by meandrew on Mon, 19 Jul 2021 07:04:11 +0930