quartz(五)结合项目使用

quartz结合项目实际使用示例

这篇文章我在项目中实际使用给大家介绍项目中如何使用quartz,前面文章介绍过定时任务的存储方式主要有两种:存储在内存的RAMJobStore和存储在数据库的JobStoreSupport,由于存储在内存中的形式,在清理缓存时会造成数据丢失,并且在做集群的时候也是采取存在数据库中的形式才可以,因此这里主要介绍的是存储在数据库中方式。

一、表结构

首先介绍的是数据库存储方式的表结构,quartz总共有11张表。

  1. qrtz_blob_triggers : 以Blob 类型存储的触发器。
  2. qrtz_calendars:存放日历信息, quartz可配置一个日历来指定一个时间范围。
  3. qrtz_cron_triggers:存放cron类型的触发器。
  4. qrtz_fired_triggers:存放已触发的触发器。
  5. qrtz_job_details:存放一个jobDetail信息。
  6. qrtz_locks: 存储程序的悲观锁的信息(假如使用了悲观锁)。
  7. qrtz_paused_trigger_graps:存放暂停掉的触发器。
  8. qrtz_scheduler_state:调度器状态。
  9. qrtz_simple_triggers:简单触发器的信息。
  10. qrtz_trigger_listeners:触发器监听器。
  11. qrtz_triggers:触发器的基本信息。

二、项目中引入依赖

        <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>

三、项目配置文件

#默认或是自己改名字都行
org.quartz.scheduler.instanceName: DefaultQuartzScheduler

#如果使用集群,instanceId必须唯一,设置成AUTO
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
#线程池类型
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
#线程池数量
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000
#存储方式使用JobStoreTX,也就是数据库
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#使用自己的配置文件
org.quartz.jobStore.useProperties:true
#数据库中quartz表的表名前缀
org.quartz.jobStore.tablePrefix:QRTZ_
org.quartz.jobStore.dataSource:qzDS
#是否使用集群(如果项目只部署到 一台服务器,就不用了)
org.quartz.jobStore.isClustered = true

org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver

org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/logtest?characterEncoding=utf8&useSSL=false&autoReconnect=true&allowMultiQueries=true

org.quartz.dataSource.qzDS.user = root

org.quartz.dataSource.qzDS.password = root

org.quartz.dataSource.qzDS.maxConnections = 10

四、配置类
配置类主要需要配置的是一些监听器及SchedulerFactoryBean,这些类中具体的方法作用介绍我在之前的文章中有介绍,大家可以根据实际情况进行使用
首先是JobListener负责Job执行情况的监听

@Component
public class JobsListener implements JobListener {
    @Override
    public String getName() {
        return "globalJob";
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        System.out.println("JobsListener.jobToBeExecuted()");
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println("JobsListener.jobExecutionVetoed()");
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        System.out.println("JobsListener.jobWasExecuted()");
    }
}

其次是TriggerListener负责触发器执行情况的监听

@Component
public class TriggerListner implements TriggerListener {
    @Override
    public String getName() {
        return "globalTrigger";
    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        System.out.println("TriggerListner.triggerFired()");
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        System.out.println("TriggerListner.vetoJobExecution()");
        return false;
    }

    @Override
    public void triggerMisfired(Trigger trigger) {
        System.out.println("TriggerListner.triggerMisfired()");
        String jobName = trigger.getKey().getName();
        System.out.println("Job name: " + jobName + " is misfired");
    }

    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {
        System.out.println("TriggerListner.triggerComplete()");
    }


初始化一个JOb工厂类,普通的job工厂不支持注入其他的Spring bean,比如咱们的DAO实例,那么我们需要自己创建一个Job工厂类进行使气支持这种操作

@Component
public class MyJobFactory extends AdaptableJobFactory {
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        // 进行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }}

最后我们创建的这些需要在SchedulerFactoryBean初始化的时候加入到SchedulerFactoryBean配置中,这样在启动时就会将这些配置也读取进去,具体流程之前一篇文章有介绍。

@Configuration
public class QuarzConfig {
    @Autowired
    DataSource dataSource;

    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private MyJobFactory myJobFactory;
    @Autowired
    private TriggerListner triggerListner;

    @Autowired
    private JobsListener jobsListener;

    /**
     * create scheduler
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {

        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setOverwriteExistingJobs(true);
        factory.setDataSource(dataSource);
        factory.setQuartzProperties(quartzProperties());

        //Register listeners to get notification on Trigger misfire etc
        factory.setGlobalTriggerListeners(triggerListner);
        factory.setGlobalJobListeners(jobsListener);
        factory.setJobFactory(myJobFactory);
        //factory.setSchedulerListeners();
        return factory;
    }

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


}

这样在启动初始化的时候就会读取我们设置好的监听及配置文件。我们通过这个工厂创建的调度器及创建Job及Trigger,就可以达到监听效果。

具体创建逻辑如下

@Component
@Slf4j
public class CreatJobUtil {
    @Autowired
    private ApplicationContext context;
    @Autowired
    @Lazy
    SchedulerFactoryBean schedulerFactoryBean;
    //可以在业务逻辑中需要创建定时的地方调用这个方法进行定时创建
    public  boolean createJob(String jobName, String triggerName,String groupName ,String recordId, Date expireTime){
        log.info("创建定时开始");
        try {
            //注意这里的scheduler一定要通过schedulerFactoryBean来创建,否则监听之类的
            //不会生效
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            String wareRecordJobName=jobName+recordId;
            String wareRecordTriggerName = triggerName+recordId;
            //createJob方法的各个参数在下面会有介绍
            JobDetail recordJob = JobUtil.createJob(WareHouseRecordJob.class, false, context, wareRecordJobName, groupName,recordId);
            //createSingleTrigger方法的各个参数在下面会有介绍
            Trigger wareRecordTrigger = JobUtil.createSingleTrigger(wareRecordTriggerName,expireTime, SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
            scheduler.scheduleJob(recordJob,wareRecordTrigger);
            return true;
        } catch (SchedulerException e) {
            e.printStackTrace();
            return false;
        }
    }
}

public class JobUtil {
    /**
     * Create Quartz Job.
     *
     * @param jobClass 到时间后去具体执行业务逻辑的JOb类
     * @param isDurable 定时执行完成后是否需要持久化,true为需要持久化,false为执行完删除
     * @param context Spring application context,spring上下文
     * @param jobName Job name. Job名字
     * @param jobGroup Job group. Job团
     * @param activeId 最后这个参数为定时执行时需要的一些参数,也可以是实体类
     * @return JobDetail object
     */
    public static JobDetail createJob( Class jobClass, boolean isDurable,
                                      ApplicationContext context, String jobName, String jobGroup,int activeId){
        JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
        factoryBean.setJobClass(jobClass);
        factoryBean.setDurability(isDurable);
        factoryBean.setApplicationContext(context);
        factoryBean.setName(jobName);
        factoryBean.setGroup(jobGroup);
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("activeid",String.valueOf(activeId));
        factoryBean.setJobDataMap(jobDataMap);
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }

    public static JobDetail createJob(Class jobClass, boolean isDurable,
                                      ApplicationContext context, String jobName, String jobGroup, String remindDto){
        JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
        factoryBean.setJobClass(jobClass);
        factoryBean.setDurability(isDurable);
        factoryBean.setApplicationContext(context);
        factoryBean.setName(jobName);
        factoryBean.setGroup(jobGroup);
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("message",remindDto);
        factoryBean.setJobDataMap(jobDataMap);
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }
    /**
     * Create cron trigger.
     *
     * @param triggerName Trigger name.触发器名字
     * @param startTime Trigger start time. 触发器开始实际
     * @param cronExpression Cron expression. cron表达式
     * @param misFireInstruction Misfire instruction (what to do in case of misfire happens). 如果触发器变为Misfire怎么处理的策略设置 
     *
     * @return Trigger
     */
    public static Trigger createCronTrigger(String triggerName, Date startTime, String cronExpression, int misFireInstruction){
        PersistableCronTriggerFactoryBean factoryBean = new PersistableCronTriggerFactoryBean();
        factoryBean.setName(triggerName);
        factoryBean.setStartTime(startTime);
        factoryBean.setCronExpression(cronExpression);
        factoryBean.setMisfireInstruction(misFireInstruction);
        try {
            factoryBean.afterPropertiesSet();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return factoryBean.getObject();
    }

    /**
     * Create a Single trigger.
     *
     * @param triggerName Trigger name.  触发器名字
     * @param startTime Trigger start time. 开始时间
     * @param misFireInstruction Misfire instruction (what to do in case of misfire happens). 如果触发器变为Misfire怎么处理的策略设置
     * 创建单次执行trigger
     * @return Trigger
     */
    public static Trigger createSingleTrigger(String triggerName, Date startTime, int misFireInstruction){
        SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
//        SimpleTrigger simpleTrigger = new SimpleTrigger();
        factoryBean.setName(triggerName);
        factoryBean.setStartTime(startTime);
        factoryBean.setMisfireInstruction(misFireInstruction);
        factoryBean.setRepeatCount(0);
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }

}

Job类具体的业务逻辑实现

@Slf4j
public class WareHouseRecordJob extends QuartzJobBean implements InterruptableJob {
    private volatile boolean toStopFlag = true;
    @Override
    public void interrupt() throws UnableToInterruptJobException {
        System.out.println("Stopping thread... ");
        toStopFlag = false;
    }

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        JobDataMap mergedJobDataMap = context.getMergedJobDataMap();
        String message = (String)mergedJobDataMap.get("message");

    }
}

注:这种写法在Job的实现类里面事务无效,如果希望使用事务可以将业务逻辑写在service层,然后在service加入事务,Job类中引入service.

至此quartz在项目中就可以使用了,可以创建SimpleTrigger和CronTrigger,也可以根据实际需求来进行设置不同的监听来在JOb执行的不同阶段进行相应操作。另目前这种写法就已经支持集群,只有确保不同服务器上操作项目一致,且时间相同即可。
下篇文章将会写如何关闭、暂停定时等操作。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容