Java-Quartz定时器

x33g5p2x  于2021-09-27 转载在 Java  
字(29.4k)|赞(0)|评价(0)|浏览(538)

Java-Quartz定时器

Quartz定时器介绍

Quartz是什么

简单来说就是一个任务调度框架,帮我们自动去执行这些程序。

大部分公司都会用到定时任务这个功能。

比如订单支付来说,超过一定时间会执行这个job,去判断你是否支付,未支付就会取消此次订单

Quartz能干什么

1: 下载交易流水

比如银行每天会自动下载流水

2: 修改密码提醒和告警

出于安全考虑,长时间没有修改密码会发出警告

3: 会员续费

会员到期了,TX提醒你进行充值

4: 短信通知

每隔一段时间向指定用户发送短信

5:垃圾清理

比如:图片上传时候用户没有提提交表单 那么这张图片就是垃圾图片我们需要进行定时清理这些垃圾图片

…太多太多能用到定时器的地方了这里就不在举例了

需要的Maven/jar

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

Quartz的原理

Quartz的原理不是很复杂,只要搞明白几个概念,然后知道如何去启动和关闭一个调度程序即可。(关闭一般用不到)

1、Job
表示一个工作,要执行的具体内容。此接口中只有一个方法
void execute(JobExecutionContext context)

2、JobDetail
JobDetail表示一个具体的可执行的调度程序,Job是这个可执行程调度程序所要执行的内容,另外JobDetail还包含了这个任务调度的方案和策略。

3、Trigger代表一个调度参数的配置,什么时候去调。

4、Scheduler代表一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger。当Trigger与JobDetail组合,就可以被Scheduler容器调度了。

Quartz入门案例

然后我们看代码 注释写的很清楚

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class TestQuartz {


    public static void main(String[] args) throws Exception{
        //创建调度器
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        //定义一个触发器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") //定义触发器名称和所属触发器的分组
                .startNow()
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever()
                        .withIntervalInSeconds(2) //每隔2秒执行一次
                        .withRepeatCount(10)) //总共执行11次(第一次执行不记数)
                .build();

        //定义一个JobDetail (任务)
        JobDetail job = JobBuilder.newJob(MailJob.class) //指定干活的类MailJob
                .withIdentity("mailjob1", "mailgroup") //定义任务名称和所属任务的分组
                .usingJobData("email", "admin@10086.com") //定义传入任务里的属性(key,value)
                .build();

        //调度加入这个job
        scheduler.scheduleJob(job, trigger);

        //启动任务
        scheduler.start();

        //等待20秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }
}
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;
import java.util.Date;

//任务类
public class MailJob  implements Job {
    //执行任务的唯一入口
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail detail = context.getJobDetail();
        String email = detail.getJobDataMap().getString("email");

        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        String now = sdf.format(new  Date());

        System.out.printf("给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s%n" ,email, now);
    }

}

结果:

给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:18
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:20
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:22
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:24
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:26
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:28
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:30
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:32
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:34
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:36
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 09:39:38

分组干什么用?

.withIdentity(“mailjob1”, “mailgroup”)

mailgroup就是分组的意思。
比如一个系统有3个job 是备份数据库的,有4个job 是发邮件的,那么对他们进行分组,可以方便管理,类似于一次性停止所有发邮件的这样的操作.

静态导入

import static 这种写法叫做静态导入,指的是导入某个类的静态方法, 这样就可以直接使用了,

newTrigger() newJob

而不是必须写成:TriggerBuilder.newTrigger() JobBuilder.newJob()

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

自己将上面的代码进行修改下

Job管理

Job 组成部分

Job 其实是由 3 个部分组成:

JobDetail: 用于描述这个Job是做什么的
1.
实现Job的类: 具体干活的
1.
JobDataMap: 给 Job 提供参数用的

JobDataMap 除了usingJobData 方式之外,还可以是其他方式,像这样
job.getJobDataMap().put(“email”, “admin@taobao.com”);

关键代码

//定义一个JobDetail
        JobDetail job = newJob(MailJob.class)
            .withIdentity("mailjob1", "mailgroup")
            .usingJobData("email", "admin@10086.com")
            .build();
         
        //用JobDataMap 修改email
        job.getJobDataMap().put("email", "admin@taobao.com");
Job 并发

默认的情况下,无论上一次任务是否结束或者完成,只要规定的时间到了,那么下一次就开始。

有时候会做长时间的任务,比如数据库备份,这个时候就希望上一次备份成功结束之后,才开始下一次备份,即便是规定时间到了,也不能开始,因为这样很有可能造成 数据库被锁死 (几个线程同时备份数据库,引发无法预计的混乱)。

那么在这种情况下,给数据库备份任务类增加一个注解就好了:
@DisallowConcurrentExecution

Job 异常

任务里发生异常是很常见的。 异常处理办法通常是两种:

  1. 当异常发生,那么就通知所有管理这个 Job 的调度,停止运行它
  2. 当异常发生,修改异常地方,然后重新运行

Job任务

public class ExceptionJob1  implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
 
        int i = 0;
        try {
            //故意发生异常
            System.out.println(100/i);
             
        } catch (Exception e) {
            System.out.println("发生了异常,取消这个Job 对应的所有调度");
            JobExecutionException je =new JobExecutionException(e);
            je.setUnscheduleAllTriggers(true);
            throw je;
        }
    }
}

测试

public static void main(String[] args) throws Exception{
        exceptionHandle1();

    }
    private static void exceptionHandle1() throws Exception {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Trigger trigger = newTrigger().withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(simpleSchedule()
                        .withIntervalInSeconds(2)
                        .withRepeatCount(10))
                .build();

        //定义一个JobDetail
        JobDetail job = newJob(ExceptionJob1.class)
                .withIdentity("exceptionJob1", "someJobGroup")
                .build();

        //调度加入这个job
        scheduler.scheduleJob(job, trigger);

        //启动
        scheduler.start();

        //等待20秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }
中断 Job

在业务上,有时候需要中断任务,那么这个Job需要实现 InterruptableJob 接口,然后就方便中断了

Job任务

//必须实现InterruptableJob 而非 Job才能够被中断
public class StoppableJob implements InterruptableJob {
    private boolean stop = false;
    public void execute(JobExecutionContext context) throws JobExecutionException {

        while(true){

            if(stop){
                break;
            }

            try {
                System.out.println("每隔1秒,进行一次检测,看看是否停止");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("持续工作中。。。");
        }

    }
    public void interrupt() throws UnableToInterruptJobException {
        System.out.println("被调度叫停");
        stop = true;
    }
}

测试

public class TestQuartz {
    public static void main(String[] args) throws Exception{
        stop();

    }
    private static void stop() throws Exception {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Trigger trigger = newTrigger().withIdentity("trigger1", "group1")
                .startNow()
                .build();

        //定义一个JobDetail
        JobDetail job = newJob(StoppableJob.class)
                .withIdentity("exceptionJob1", "someJobGroup")
                .build();

        //调度加入这个job
        scheduler.scheduleJob(job, trigger);

        //启动
        scheduler.start();

        Thread.sleep(5000);
        System.out.println("过5秒,调度停止 job");

        //key 就相当于这个Job的主键
        scheduler.interrupt(job.getKey());

        //等待20秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }

}

SimpleTrigger

Trigger 就是触发器的意思,用来指定什么时间开始触发,触发多少次,每隔多久触发一次.
SimpleTrigger 可以方便的实现一系列的触发机制。

下一个8秒的倍数

job任务

@DisallowConcurrentExecution
public class MailJob implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail detail = context.getJobDetail();

        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        String now = sdf.format(new Date());

        System.out.printf("发出了一封邮件, 当前时间是: %s%n" , now);
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试

public static void main(String[] args) throws Exception{
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

        JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

        SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();

        // schedule it to run!
        Date ft = scheduler.scheduleJob(job, trigger);

        System.out.println("当前时间是:" + new Date().toLocaleString());
        System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

        scheduler.start();

        //等待200秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(200000);
        scheduler.shutdown(true);
    }

当前时间是:2021-3-7 22:41:27
mailGroup.mailJob 这个任务会在 2021-3-7 22:41:32 准时开始运行,累计运行1次,间隔时间是0毫秒
发出了一封邮件, 当前时间是: 22:41:32

10 秒后运行

使用方法:

DateBuilder.futureDate()

可以方便的获取10秒后, 5分钟后, 3个小时候,2个月后…这样的时间

job任务 还是上面那个案例MailJob的类

测试

public static void main(String[] args) throws Exception{
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Date startTime = DateBuilder.futureDate(10, DateBuilder.IntervalUnit.SECOND);

        JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

        SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();

        // schedule it to run!
        Date ft = scheduler.scheduleJob(job, trigger);

        System.out.println("当前时间是:" + new Date().toLocaleString());
        System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

        scheduler.start();

        //等待200秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(200000);
        scheduler.shutdown(true);
    }

当前时间是:2021-3-7 22:46:57
mailGroup.mailJob 这个任务会在 2021-3-7 22:47:07 准时开始运行,累计运行1次,间隔时间是0毫秒
发出了一封邮件, 当前时间是: 22:47:07

注意 DateBuilder.IntervalUnit这里面包含了

MILLISECOND 毫秒

SECOND 秒

MINUTE分

HOUR时

DAY天

WEEK周

MONTH 月

YEAR年

累计n次,间隔n秒

job任务 还是上面那个案例MailJob的类

测试

public static void main(String[] args) throws Exception{
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

        JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

        SimpleTrigger trigger = (SimpleTrigger) newTrigger()
                .withIdentity("trigger1", "group1")
                .startAt(startTime)
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever()
                        .withRepeatCount(3)//执行次数 算上原本的一次一共4次
                        .withIntervalInSeconds(1)) //每一秒执行一次
                .build();

        // schedule it to run!
        Date ft = scheduler.scheduleJob(job, trigger);

        System.out.println("当前时间是:" + new Date().toLocaleString());
        System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

        scheduler.start();

        //等待200秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(200000);
        scheduler.shutdown(true);
    }

当前时间是:2021-3-7 22:54:45
mailGroup.mailJob 这个任务会在 2021-3-7 22:54:48 准时开始运行,累计运行4次,间隔时间是1000毫秒
发出了一封邮件, 当前时间是: 22:54:48
发出了一封邮件, 当前时间是: 22:54:49
发出了一封邮件, 当前时间是: 22:54:50
发出了一封邮件, 当前时间是: 22:54:51

无限重复,间隔1秒

job任务 还是上面那个案例MailJob的类

测试

public static void main(String[] args) throws Exception{
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

        JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

        SimpleTrigger trigger = (SimpleTrigger) newTrigger()
                .withIdentity("trigger1", "group1")
                .startAt(startTime)
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever()
                        .repeatForever() //表示无限次数
                        .withIntervalInSeconds(1)) //每1秒执行一次
                .build();

        // schedule it to run!
        Date ft = scheduler.scheduleJob(job, trigger);

        System.out.println("当前时间是:" + new Date().toLocaleString());
        System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

        scheduler.start();

        //等待200秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(200000);
        scheduler.shutdown(true);
    }

无限重复的累计次数是显示的0,因为没法表示无限。。

CronTrigger

CronTrigger 是什么?

Cron 是Linux下的一个定时器,功能很强大,但是表达式更为复杂
CronTrigger 就是用 Cron 表达式来安排触发时间和次数的。

因为Cron 特别复杂,如果以前接触过,可以按照Cron表达式来做触发器,没有接触过,还是用SimpleTrigger吧。。。

这个Cron 表达式就表示每隔2秒执行一次
0/2 /* /* /* /* ?

job任务 还是上面那个案例MailJob的类

public static void main(String[] args) throws Exception{
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

        JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

        CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
                .build();

        // schedule it to run!
        Date ft = scheduler.scheduleJob(job, trigger);

        System.out.println("使用的Cron表达式是:"+trigger.getCronExpression());
// System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

        scheduler.start();

        //等待200秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(200000);
        scheduler.shutdown(true);
    }

发出了一封邮件, 当前时间是: 23:03:28
发出了一封邮件, 当前时间是: 23:03:30
发出了一封邮件, 当前时间是: 23:03:32
发出了一封邮件, 当前时间是: 23:03:34
发出了一封邮件, 当前时间是: 23:03:36
发出了一封邮件, 当前时间是: 23:03:38
发出了一封邮件, 当前时间是: 23:03:40
发出了一封邮件, 当前时间是: 23:03:42
发出了一封邮件, 当前时间是: 23:03:44
发出了一封邮件, 当前时间是: 23:03:46

如结果所示 每隔2秒执行一次。为什么这个表达式就是这个效果呢? 看下一步 理解Cron

理解Cron

0/2 /* /* /* /* ?

由7个部分组成,每个部分就如图所示分别对应 秒分 一直到年

星号(/*):可用在所有字段中,表示对应时间域的每一个时刻,例如, 在分钟字段时,表示“每分钟”;
问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;
减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12;
逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;

这里有个Cron表达式生成工具,可以参考一下: http://cron.qqe2.com/

但是注意这个网站生成的是标准的7位 但是在java中只能是6位 需要把最后的/*去掉

比如: 0 59 23 /* /* ?/* 在java中是 0 59 23 /* /* ?

Cron 表达式举例

监听器

Quartz 的监听器有Job监听器,Trigger监听器, Scheduler监听器,对不同层面进行监控。 实际业务用的较多的是Job监听器,用于监听器是否执行了,其他的用的相对较少,本知识主要讲解Job的。

MailJobListener 实现了 JobListener 接口,就必须实现如图所示的4个方法。

job任务 还是上面那个案例MailJob的类

创建监听类 MailJobListener

public class MailJobListener implements JobListener {

    @Override
    public String getName() {
        return "listener of mail job";
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println("取消执行:\t "+context.getJobDetail().getKey());
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        System.out.println("准备执行:\t "+context.getJobDetail().getKey());
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException arg1) {
        System.out.println("执行结束:\t "+context.getJobDetail().getKey());
        System.out.println();
    }

}

测试

public static void main(String[] args) throws Exception{
        //创建调度器
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        //定义一个触发器
        Trigger trigger = newTrigger().withIdentity("trigger1", "group1") //定义名称和所属的租
                .startNow()
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever()
                        .withIntervalInSeconds(2) //每隔2秒执行一次
                        .withRepeatCount(10)) //总共执行11次(第一次执行不记数)
                .build();

        //定义一个JobDetail
        JobDetail mailJob = newJob(MailJob.class) //指定干活的类MailJob
                .withIdentity("mailjob1", "mailgroup") //定义任务名称和分组
                .usingJobData("email", "admin@10086.com") //定义属性
                .build();

        //增加Job监听
        MailJobListener mailJobListener= new MailJobListener();
        KeyMatcher<JobKey> keyMatcher = KeyMatcher.keyEquals(mailJob.getKey());
        scheduler.getListenerManager().addJobListener(mailJobListener, keyMatcher);

        //调度加入这个job
        scheduler.scheduleJob(mailJob, trigger);

        //启动
        scheduler.start();

        //等待20秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }

JDBCStore

JDBCStore 概念

默认情况,Quartz的触发器,调度,任务等信息都是放在内存中的,叫做 RAMJobStore。 好处是快速,坏处是一旦系统重启,那么信息就丢失了,就得全部从头来过。
所以Quartz还提供了另一个方式,可以把这些信息存放在数据库做,叫做 JobStoreTX。 好处是就算系统重启了,目前运行到第几次了这些信息都是存放在数据库中的,那么就可以继续原来的步伐把计划任务无缝地继续做下去。 坏处就是性能上比内存慢一些,毕竟数据库读取总是要慢一些的。

建表 sql

为了能够把相关信息存放进 mysql 数据库里,必须手动建立数据库和表,使用如下 脚本就行了。
注: 这里使用的数据库名称是 quartz

DROP DATABASE IF EXISTS quartz;
CREATE DATABASE quartz DEFAULT CHARACTER SET utf8;
USE quartz;
 
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
 
CREATE TABLE QRTZ_JOB_DETAILS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME  VARCHAR(100) NOT NULL,
    JOB_GROUP VARCHAR(100) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
 
CREATE TABLE QRTZ_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    JOB_NAME  VARCHAR(100) NOT NULL,
    JOB_GROUP VARCHAR(100) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(100) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
 
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    REPEAT_COUNT BIGINT(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
 
CREATE TABLE QRTZ_CRON_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    CRON_EXPRESSION VARCHAR(100) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
 
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (         
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
 
CREATE TABLE QRTZ_BLOB_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
 
CREATE TABLE QRTZ_CALENDARS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME  VARCHAR(100) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
 
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR(100) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
 
CREATE TABLE QRTZ_FIRED_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    INSTANCE_NAME VARCHAR(100) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(100) NULL,
    JOB_GROUP VARCHAR(100) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
 
CREATE TABLE QRTZ_SCHEDULER_STATE
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(100) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
 
CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
 
commit;

配置文件

在src下的resources内新建 quartz.properties 配置文件,里面指定使用 JobStoreTX 方式管理任务。 并且指定链接数据库的账号密码等信息

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource = mysqlDatabase

org.quartz.dataSource.mysqlDatabase.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.mysqlDatabase.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8
org.quartz.dataSource.mysqlDatabase.user = root
org.quartz.dataSource.mysqlDatabase.password = root
org.quartz.dataSource.mysqlDatabase.maxConnections = 5

注意添加JDBC驱动包

<!--java JDBC-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

job任务

@DisallowConcurrentExecution
public class MailJob implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {

        JobDetail detail = context.getJobDetail();
        String email = detail.getJobDataMap().getString("email");

        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        String now = sdf.format(new  Date());

        System.out.printf("给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s (%s)%n" ,email, now,context.isRecovering());
    }
}

测试

public class TestQuartz {
    public static void main(String[] args) throws Exception {
        try {
            assginNewJob();
        } catch (ObjectAlreadyExistsException e) {
            System.err.println("发现任务已经在数据库存在了,直接从数据库里运行:"+ e.getMessage());
            resumeJobFromDatabase();
        }
    }

    private static void resumeJobFromDatabase() throws Exception {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        // 等待200秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(200000);
        scheduler.shutdown(true);
    }

    private static void assginNewJob() throws SchedulerException, InterruptedException {
        // 创建调度器
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        // 定义一个触发器
        Trigger trigger = newTrigger().withIdentity("trigger1", "group1") // 定义名称和所属的租
                .startNow()
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever().withIntervalInSeconds(15) // 每隔15秒执行一次
                        .withRepeatCount(10)) // 总共执行11次(第一次执行不基数)
                .build();

        // 定义一个JobDetail
        JobDetail job = newJob(MailJob.class) // 指定干活的类MailJob
                .withIdentity("mailjob1", "mailgroup") // 定义任务名称和分组
                .usingJobData("email", "admin@10086.com") // 定义属性
                .build();

        // 调度加入这个job
        scheduler.scheduleJob(job, trigger);

        // 启动
        scheduler.start();

        // 等待20秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }

第一次运行的结果

给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:56:42 (false)
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:56:56 (false)

第二次运行的结果

发现任务已经在数据库存在了,直接从数据库里运行:Unable to store Job : ‘mailgroup.mailjob1’, because one already exists with this identification.

给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:57:12 (false)
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:57:26 (false)
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:57:41 (false)
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:57:56 (false)
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:58:11 (false)
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:58:26 (false)
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:58:41 (false)
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:58:56 (false)
给邮件地址 admin@10086.com 发出了一封定时邮件, 当前时间是: 00:59:11 (false)

解析这是为什么

jDBCStore每次执行任务前都会去数据库里找这个任务有没有,没有执行完的任务如果有就接着上回继续执行

如果没有就 按照现在的新任务开始

上面代码结果解析:

你第一次运行时候会去数据库里找发现没有此任务,未执行完的数据,那么就按照现在的新任务开始然后你发现

发现你执行2次就关闭了,这是我故意的

注意:看代码我们只让主线程停止了20每秒而job任务是每15秒执行一次也就是开始的时候执行一次 然后过15秒后执行一次之后就运行了scheduler.shutdown(true);将任务强制关闭了.

主要是测试是否下次从新运行此任务是否从上次断开的时候继续运行剩下的次数

然后你去数据库qrtz_simple_triggers表里去看看 这个表里记录里一共需要执行几次和你当前执行了几次

然后你第二次执行的时候他会从数据库里看看有没有,没有运行完毕的任务记录如果有就接着上回继续执行

直到执行完剩下的9次后自动将数据库数据删除,你在去qrtz_simple_triggers表里就会发现没数据了

集群概念

这所谓的Quartz集群,是指在 基于数据库存储 Quartz调度信息的基础上, 有多个一模一样的 Quartz 应用在运行。
当某一个Quartz 应用重启或者发生问题的时候, 其他的Quartz 应用会借助数据库这个桥梁探知到它不行了,从而接手把该进行的Job调度工作进行下去。
以这种方式保证任务调度的高可用性,即在发生异常重启等情况下,调度信息依然连贯性地进行下去,就好像 Quartz 应用从来没有中断过似的。
注: 文中描述的 Quartz 应用 在一些语境下,又叫做 Quartz 服务器节点,都是同一个概念。

TestQuartz 不需要做改动,沿用 JDBC Store里的 TestQuartz.java 就行

而表也沿用 JDBC Store里的不要动还是原来的

quartz.properties 在原来的基础上,增加和修改下这4行:

开启集群
org.quartz.jobStore.isClustered = true

要进行集群,多个应用调度名称 instanceName 应该是一样的

org.quartz.scheduler.instanceName = quartzScheduler

要进行集群,多个应用调度id instanceId 必须不一样,这里使用AUTO,就会自动分配不同的ID。 目测是本机机器名称加上时间戳

org.quartz.scheduler.instanceId = AUTO

每个一秒钟去数据库检查一下,以在其他应用挂掉之后及时补上

org.quartz.jobStore.clusterCheckinInterval = 1000

完整版

org.quartz.jobStore.isClustered = true
org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.clusterCheckinInterval = 1000

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = mysqlDatabase

org.quartz.dataSource.mysqlDatabase.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.mysqlDatabase.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8
org.quartz.dataSource.mysqlDatabase.user = root
org.quartz.dataSource.mysqlDatabase.password = root
org.quartz.dataSource.mysqlDatabase.maxConnections = 5

job任务还是 JDBCStore里的MailJob类

测试代码

public static void main(String[] args) throws Exception {
        try {
            assginNewJob();
        } catch (ObjectAlreadyExistsException e) {
            System.err.println("发现任务已经在数据库存在了,直接从数据库里运行:"+ e.getMessage());
            resumeJobFromDatabase();
        }
    }

    private static void resumeJobFromDatabase() throws Exception {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        System.out.println("当前调度器的id是:"+scheduler.getSchedulerInstanceId());
        scheduler.start();
        // 等待200秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(200000);
        scheduler.shutdown(true);
    }

    private static void assginNewJob() throws SchedulerException, InterruptedException {
        // 创建调度器
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        // 定义一个触发器
        Trigger trigger = newTrigger().withIdentity("trigger1", "group1") // 定义名称和所属的租
                .startNow()
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever()
                        .withIntervalInSeconds(15) // 每隔15秒执行一次
                        .withRepeatCount(10)) // 总共执行11次(第一次执行不基数)
                .build();

        // 定义一个JobDetail
        JobDetail job = newJob(MailJob.class) // 指定干活的类MailJob
                .withIdentity("mailjob1", "mailgroup") // 定义任务名称和分组
                .usingJobData("email", "admin@10086.com") // 定义属性
                .build();

        // 调度加入这个job
        scheduler.scheduleJob(job, trigger);
        System.out.println("当前调度器的id是:"+scheduler.getSchedulerInstanceId());

        // 启动
        scheduler.start();

        // 等待20秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }

Quartz定时工具类

实体类BaseJob

public class BaseJob implements Serializable {

    private String jobName;   //任务名称
    private String jobGroupName;  //任务组名称
    private String triggerName; //触发器名称
    private String triggerGroupName;//触发器组名称
    private String cronTime;  //crond格式时间
    private JobDataMap jobDataMap;  //附带参数
    private  int time;  //时间间隔 秒
    private  int  count; //执行多少次
    private Class jobClass;
 
    ......get  set
}

QuartzUtil

public class QuartzUtil {
    /** * [简单任务调度:每次执行间隔为多少毫秒,执行多少次] <br> */
    public static void handleSimpleTrigger(BaseJob baseJob) {

        // 通过schedulerFactory获取一个调度器
        SchedulerFactory schedulerfactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            // 通过schedulerFactory获取一个调度器
            scheduler = schedulerfactory.getScheduler();
            // 创建jobDetail实例,绑定Job实现类
            // 指明job的名称,所在组的名称,以及绑定job类
            JobDetail job = JobBuilder.newJob(baseJob.getJobClass())
                    .withIdentity(baseJob.getJobName(),baseJob.getJobGroupName() )
                    .usingJobData(baseJob.getJobDataMap())
                    .build();
            // 定义调度触发规则
            //使用simpleTrigger规则
            Trigger
                    trigger=TriggerBuilder.newTrigger().withIdentity(baseJob.getTriggerName(),
                    baseJob.getJobGroupName())
                    .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(baseJob.getTime()).withRepeatCount(baseJob.getCount()))
                    .startNow().build();
            // 把作业和触发器注册到任务调度中
            scheduler.scheduleJob(job, trigger);
            // 启动调度
            scheduler.start();

        } catch (Exception e) {
                e.printStackTrace();
        }


    }
    /** * [复杂任务调度:每天几点几分几秒定时执行任务] */
    public static void hadleCronTrigger(BaseJob baseJob) {
        // 通过schedulerFactory获取一个调度器
        SchedulerFactory schedulerfactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            // 通过schedulerFactory获取一个调度器
            scheduler = schedulerfactory.getScheduler();
            // 创建jobDetail实例,绑定Job实现类
            // 指明job的名称,所在组的名称,以及绑定job类
            JobDetail job = JobBuilder.newJob(baseJob.getJobClass())
                    .withIdentity(baseJob.getJobName(), baseJob.getJobGroupName())
                    .usingJobData(baseJob.getJobDataMap())
                    .build();
            // 定义调度触发规则
            //使用cornTrigger规则 每天18点30分
            Trigger trigger=TriggerBuilder.newTrigger().withIdentity(baseJob.getTriggerName(), baseJob.getTriggerGroupName())
                    .withSchedule(CronScheduleBuilder.cronSchedule(baseJob.getCronTime()))
                    .startNow().build();
            // 把作业和触发器注册到任务调度中
            scheduler.scheduleJob(job, trigger);
            // 启动调度
            scheduler.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



}

这个工具类主要就是 各种调用job任务的

然后我们开始测试

创建2个job任务

MyJob 和 My1Job

public class MyJob implements Job {

    @Override
    public void execute(JobExecutionContext context)  {
        System.out.println("MyJob Hello World");
        // 编写具体的业务逻辑
        JobKey key = context.getJobDetail().getKey();
        System.out.println("当前任务名称:" + key.getName() + " 当前任务组名称:: " + key.getGroup());

        TriggerKey trkey = context.getTrigger().getKey();
        System.out.println("当前触发器的名称: " + trkey.getName() + " 当前触发器组的名称: " + trkey.getGroup());

        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        Object test = jobDataMap.get("test");
        System.out.println(test);
    }

}
public class My1Job implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("My1Job----- Hello World");
// 编写具体的业务逻辑
        JobKey key = context.getJobDetail().getKey();
        System.out.println("当前任务名称:" + key.getName() + " 当前任务组名称:: " + key.getGroup());

        TriggerKey trkey = context.getTrigger().getKey();
        System.out.println("当前触发器的名称: " + trkey.getName() + " 当前触发器组的名称: " + trkey.getGroup());

        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        Object test = jobDataMap.get("test1");
        System.out.println(test);
        
    }

}

测试类Test

public class Test {

    public static void main(String[] args) {


        BaseJob baseJob = new BaseJob();
        baseJob.setJobClass(MyJob.class);
        baseJob.setJobName("jobname");//任务名称
        baseJob.setJobGroupName("jobgroupname");//任务组名称
        baseJob.setTriggerName("triggerName");//触发器名称
        baseJob.setTriggerGroupName("triggerGroupNam");//触发器组名称
        JobDataMap jobDataMap = new JobDataMap(); //向job传入参数
        jobDataMap.put("test", "test");
        baseJob.setJobDataMap(jobDataMap);
        baseJob.setTime(1);//每秒执行1次
        baseJob.setCount(8);//执行8次结束

        //简单任务调度,每隔多少时间执行一次,执行n次
        QuartzUtil.handleSimpleTrigger(baseJob);

        BaseJob baseJob1 = new BaseJob();
        baseJob1.setJobClass(My1Job.class);
        baseJob1.setJobName("jobname1");//任务名称
        baseJob1.setJobGroupName("jobgroupname1");//任务组名称
        baseJob1.setTriggerName("triggerName1");//触发器名称
        baseJob1.setTriggerGroupName("triggerGroupNam1");//触发器组名称
        JobDataMap jobDataMap1 = new JobDataMap(); //向job传入参数
        jobDataMap1.put("test1", "test1");
        baseJob1.setJobDataMap(jobDataMap1);
        baseJob1.setCronTime("*/3 * * * * ?");  //cron语法 每3秒执行一次 一直执行

        //复杂调度,每天的什么时候执行任务
        QuartzUtil.hadleCronTrigger(baseJob1);
    }

}

我们一般都是配合SSM或者SpringBoot进行使用的,可以在我博客里找对应教程

相关文章