[Development] 14. SpringBoot integrates Quartz to implement scheduled tasks

Article directory

  • 1. About scheduled tasks
  • 2. Java native implementation
  • 3. Related nouns
  • 4. SpringBoot integrates Quartz
  • 5. General configuration of Quartz
  • 6. About QuartzJobBean
  • 7. About the binding of scheduler Scheduler
  • 8. Quartz persistence

1. About scheduled tasks

There are many usage scenarios for scheduled tasks in actual development, such as:

  • annual report
  • Various statistical reports
  • a sync task

As for the implementation technologies of scheduled tasks, the popular ones in the industry are:

  • Quartz
  • Spring Task

2. Java native implementation

The demo code is as follows: TimerTask is an abstract class, and its new object must implement methods. As for the run method in it, you should be able to imagine that multi-threads also have a run, and the scheduled task is indeed this run. When the time is up, it will Go and open an asynchronous thread to perform the task. (TimerTask implements the Runnable interface)

import java.util.Timer;
import java.util.TimerTask;

public class TaskTest{<!-- -->

public static void main(String[] args){<!-- -->
\t
Timer timer = new Timer();
\t\t
TimerTask task = new TimerTask(){<!-- -->
@Override
public void run(){<!-- -->
System.out.println("task...");
}
}
\t\t
timer.schedule(task,0,2000); //Pass in the task to be executed, 0 means starting from now, 2000 means executing it every two seconds
}
}
</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

Under simple execution, the effect is to output the following tasks every two seconds…

The above is the implementation of native Java. On this basis, a more complete and standardized implementation technology Quartz appeared on the market. Therefore, Spring integrated Quartz. Later, Spring launched its own Spring Task

3. Related nouns

  • Job: used to define the specific work to be performed
  • Job Details (JobDetail): used to describe information related to scheduled work
  • Trigger: used to describe rules that trigger work. Cron expressions are usually used to define scheduling rules.
  • Scheduler: describes the correspondence between work details and triggers

Among them, Job is bound to JobDetail, and JobDetail is bound to Trigger.

4. SpringBoot integrates Quartz

Import Quartz starting dependency coordinates:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

Inherit QuartzJobBean and define the specific tasks to be performed:

public class QuartzTaskBean extends QuartzJobBean {<!-- -->
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {<!-- -->

//...Write the logic code of the scheduled task here
System.out.println("quartz job run... ");
}
}

Define work details and triggers, and bind the corresponding relationships:

@Configuration
public class QuartzConfig {<!-- -->

@Bean
public JobDetail printJobDetail(){<!-- -->
return JobBuilder.newJob(QuartzTaskBean.class) //Pass in the above Job class to bind it to JobDetail
.storeDurably() //Persistence, the storeDurably() method is used for persistence, that is, if the Job is not used at the time after it is created, do you want to persist it and save it first?
.build();
    }
    
    @Bean
    public Trigger printJobTrigger() {<!-- -->
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/3 * * * * ?");
return TriggerBuilder.newTrigger()
    .forJob(printJobDetail()) //Bind JobDetail
            .withSchedule(cronScheduleBuilder)
            .build();
}
}

</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

After the integration is completed, restart the service to see the effect:

5. General configuration of Quartz

In the above integration, there is no need to add any configuration, and the scheduled tasks are completed using the default configuration. But you can also configure Quartz in more detail in the service’s yml configuration file. (optional)

spring:
  # Quartz configuration, corresponding to the QuartzProperties configuration class
  quartz:
    job-store-type: memory # Job storage type. The default is memory which means memory, optional jdbc uses database.
    auto-startup: true # Whether Quartz starts automatically
    startup-delay: 0 # Delay startup for N seconds
    wait-for-jobs-to-complete-on-shutdown: true # Whether to wait for scheduled task execution to complete when the application is closed. The default is false, it is recommended to set it to true
    overwrite-existing-jobs: false # Whether to overwrite the configuration of existing jobs
    properties: # Add Quartz Scheduler additional properties
      org:
        quartz:
          threadPool:
            threadCount: 25 # Thread pool size. Default is 10 .
            threadPriority: 5 # Thread priority
            class: org.quartz.simpl.SimpleThreadPool # Thread pool type
# jdbc: # Not explained here for the time being, configuration is only required when using JDBC's JobStore

</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

These yaml configurations naturally have corresponding entity classes to read and receive. This entity class is QuartzProperties with the @ConfigurationProperties annotation:

6. About QuartzJobBean

Source code of the QuartzJobBean class: it implements Job and defines the public execute method

public abstract class QuartzJobBean implements Job {<!-- -->

/**
* This implementation applies the passed-in job data map as bean property
* values, and delegates to {@code executeInternal} afterwards.
* @see #executeInternal
*/
@Override
public final void execute(JobExecutionContext context) throws JobExecutionException {<!-- -->
try {<!-- -->
             //Wrap the current object as BeanWrapper
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            //Set properties
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValues(context.getScheduler().getContext());
pvs.addPropertyValues(context.getMergedJobDataMap());
bw.setPropertyValues(pvs, true);
}
catch (SchedulerException ex) {<!-- -->
throw new JobExecutionException(ex);
}
        // Subclass implements this method
executeInternal(context);
}

/**
* Execute the actual job. The job data map will already have been
* applied as bean property values by execute. The contract is
* exactly the same as for the standard Quartz execute method.
* @see #execute
*/
protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;

}

</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

Therefore, when creating a Job, you can inherit the QuartzJobBean class or directly implement the Job interface. However, inheriting QuartzJobBean and implementing the executeInternal method is of course the most efficient.

7. About the binding of scheduler

There are two ways to bind Scheduler, one is automatic configuration using bena, and the other is manual configuration using Scheduler. When integrating above, automatic configuration is used, that is, two beans and the relationship between them are defined, and then handed over to the Spring container for management. Here is another way to add manual configuration: Implement Spring's ApplicationRuunner interface, inject Quartz's Scheduler object, and call the scheduleJob method to complete manual binding.

@Component
public class QuartzJob implements ApplicationRunner {<!-- -->

    @Resource
    private Scheduler scheduler;

    @Override
    public void run(ApplicationArguments args) throws Exception {<!-- -->
    //Create JobDetail object
        JobDetail jobDetail = JobBuilder.newJob(QuartzTaskBean.class)
                .storeDurably()
                .build();
        // Simple scheduling plan constructor
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(5) // frequency
                .repeatForever(); // times
                
        //Create task trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withSchedule(scheduleBuilder)
                .startNow() //Execute a task immediately
                .build();
                
        // Manually bind triggers and tasks to the scheduler
        scheduler.scheduleJob(jobDetail, trigger);
    }
}


</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

Note that this class needs to be registered as a Bean, and remember to place it in a directory that can be scanned by the Spring container. In addition, there is no scheduler using Cron expressions above. Instead, a simple scheduling plan is used. Just call the method provided by this class to describe your execution rules.

8. Quartz persistence

Quartz persistence configuration provides two types of storage: RAMJobStore and JDBCJobStore, which are memory and hard disk: the default is to maintain task information in memory form, which means that the task will be restarted from scratch when the service is restarted, and all previous execution data will be lost.

Storage method Advantages Disadvantages
RAMJobStore Does not require an external database, is easy to configure, and runs fast Because the scheduler information is stored in the memory allocated to the JVM, when the application stops running, All scheduling information will be lost. In addition, because it is stored in the JVM memory, the number of Jobs and Triggers that can be stored will be limited
JDBCJobStore supports clustering because all task information It will be saved in the database, and things can be controlled. In addition, if the application server is shut down or restarted, the task information will not be lost, and tasks that failed due to server shutdown or restart can be recovered. The running speed depends on the speed of connecting to the database

To implement persistence to MySQL, first create the Quartz database and the script is:

org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar!\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql

Here are SQL scripts for various database types. View the table after executing MySQL:

mysql> use quartz;
Database changed
mysql> show tables;
 +--------------------------+
| Tables_in_quartz |
 +--------------------------+
| qrtz_blob_triggers |## blog type storage triggers
| qrtz_calendars |## Store Calendar information in blog type
| qrtz_cron_triggers |## Store cron trigger information
| qrtz_fired_triggers |## Stores information about triggered triggers
| qrtz_job_details |## Stores the details of each configured job
| qrtz_locks |## Stores pessimistic lock information
| qrtz_paused_trigger_grps |## Stores paused trigger group information
| qrtz_scheduler_state |## Stores Scheduler status information
| qrtz_simple_triggers |## Store simple trigger information
| qrtz_simprop_triggers |## Stores several other trigger information
| qrtz_triggers |## Store configured trigger information
 +--------------------------+


</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

Add MySQL driver and JDBC dependency coordinates (depending on the technology selection, if you use MySQL driver + MyBatis, you don’t need to add it. The MyBatis starting dependencies include Jdbc Starter below)

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<!--Look at the existing dependencies of the current project. Many framework starters that encapsulate JDBC include jdbc-starter-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!--Choose your own data source. The druid I use must be available for the projects I usually take over-->

Modify application.yml:

# Old data source configuration, using druid
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test_quartz?serverTimezone=GMT+8
      username: root
      password: root123

  #Key configuration of quartz persistence
  quartz:
    job-store-type: jdbc # Use database storage
    scheduler-name: testScheduler #Nodes with the same Scheduler name form a Quartz cluster
    wait-for-jobs-to-complete-on-shutdown: true # Whether to wait for scheduled task execution to complete when the application is closed. The default is false, it is recommended to set it to true
    jdbc:
      initialize-schema: never # Whether to automatically initialize the Quartz table structure using SQL. Set to never here, we create the table structure manually. You can choose always, never, or embedded. You can also be lazy and let the framework create the table for the first time.
</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

Restart the service and you can see that persistence is successful:

In addition to the above key configurations, for configurations related to Quartz Scheduler additional attributes such as clusters and thread pools, please refer to the following COPY’s relatively complete configuration file:

spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test_quartz?serverTimezone=GMT+8
      username: root
      password: root123
  quartz:
    job-store-type: jdbc # Use database storage
    scheduler-name: hyhScheduler #Nodes with the same Scheduler name form a Quartz cluster
    wait-for-jobs-to-complete-on-shutdown: true # Whether to wait for scheduled task execution to complete when the application is closed. The default is false, it is recommended to set it to true
    jdbc:
      initialize-schema: never # Whether to automatically initialize the Quartz table structure using SQL. Set to never here, we create the table structure manually. You can choose always, never, or embedded. You can also be lazy and let the framework create the table for the first time.
    properties:
      org:
        quartz:
          # JobStore related configuration
          jobStore:
            dataSource: quartzDataSource #Data source used
            class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore implementation class
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_ # Quartz table prefix
            isClustered: true # It is cluster mode
            clusterCheckinInterval: 1000
            useProperties: false
          # Thread pool related configuration
          threadPool:
            threadCount: 25 # Thread pool size. Default is 10 .
            threadPriority: 5 # Thread priority
            class: org.quartz.simpl.SimpleThreadPool # Thread pool type


</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

There is a scheduler-name field in the configuration. Looking at the tables in the Quartz library above, all tables contain a SCHED_NAME field. Corresponding to the configured scheduler-name, nodes with the same Scheduler-name form a Quartz cluster.