一,quartz简介
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。Quartz的最新版本为Quartz 2.3.0。
详细参考百度百科:https://baike.baidu.com/item/quartz/3643055?fr=aladdin
二,定时器种类
quartz中有5种类型的Trigger:
1)SimpleTrigger
2)CornTrigger
3)DateIntervalTrigger
4)NthIncludedDayTrigger
5)Caleendar
最常用的为
SimpleTrigger:用来触发只执行一次的或者在给定时间触发并且重复多次且每次执行延迟一定时间的任务。
CronTrigger:按照日历触发,比如每个周五,每天10点等等。
三,quartz两种调度信息存储方式
在业务场景中,不仅有定时执行的一些任务,比如每天几点执行一次,几小时几分几秒执行一次的定时任务,也有需要动态增加删除调整执行频率的定时任务。
quartz提供的执行任务的存储方式分为以下两种:
1)RAMJobStore:内存存储,不需要外部存储,运行速度快,配置简单。因为调度信息是存储在被分配给jvm的内存里面,所以当应用程序终止时,所有的调度信息都会丢失。另外也因为是存储在jvm的内存中,所以可以存储多少个Job和Trigger将会受到限制。
2)JDBCJobStore:数据库存储方式,持久化到数据库中。支持集群,因为所有的任务信息都会保存到数据库中,可以控制事务,如果服务器关闭或者重启,任务信息都不会丢失。并且可以恢复因服务器关闭或者重启而导致执行失败的任务。但这种方式的配置较复杂,速度受限于连接数据库的速度。
下面我们介绍下如何将任务信息持久化到数据库中,即JDBCJobStore存储方式的定时任务的实现例子。
四,JDBCJobStore存储方式定时任务例子
1)引入相关依赖
本人的工程使用的是Brixton.SR5 spring-cloud版本,spring-boot还应该引入spring-context-support包
<dependency>
<groupId>org.quartz-schedulergroupId>
<artifactId>quartzartifactId>
<version>2.3.0version>
dependency>
2)配置quartz.properties配置文件
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
#默认或是自己改名字都行
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
#============================================================================
# Configure JobStore
#============================================================================
#
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#存储方式使用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:quartzDs
#是否使用集群(如果项目只部署到 一台服务器,就不用了)
org.quartz.jobStore.isClustered = true
#============================================================================
# Configure Datasources
#============================================================================
#配置数据源
org.quartz.dataSource.quartzDs.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.quartzDs.URL:jdbc:mysql://192.168.1.16:3307/quartz-db
org.quartz.dataSource.quartzDs.user:quartz
org.quartz.dataSource.quartzDs.password:quartz
org.quartz.dataSource.quartzDs.maxConnections:10
3)在数据库中创建相应的表,这个可以在引入的maven包中找到:
本人用的是mysql的innodb数据库,所以选择了tables_mysql_innodb.sql脚本,用它来创建你的数据表就ok了。
4)自定义JobFactory,解决spring不能在quartz注入bean的问题:
package com.test.quartz.task;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
@Component
public class QuartzJobFactory extends AdaptableJobFactory{
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance); //这一步解决不能spring注入bean的问题
return jobInstance;
}
}
5)创建调度器Schedule
package com.test.quartz.task;
import java.io.IOException;
import java.util.Properties;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@Configuration
public class QuartzConfigration {
@Autowired
private QuartzJobFactory quartzJobFactory ; // 自定义的factory
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
try {
schedulerFactoryBean.setQuartzProperties(quartzProperties());
schedulerFactoryBean.setJobFactory(quartzJobFactory);
} catch (IOException e) {
e.printStackTrace();
}
return schedulerFactoryBean;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
@Bean(name = "scheduler")
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
6)更新quartz中的任务
首先我们创建一张自己的存储任务信息的表,里面有这些信息,可根据自己的业务需要调整:
public class CalcPlanQuartz implements Serializable {
private static final long serialVersionUID = 1L;
/**自增id*/
private Long id;
/**业务表中的计划id*/
private Long planId;
/**是否启用*/
private Integer isWork;
/**quartz框架执行频率表达式*/
private String planCorn;
/**执行计划名称,为类的全称*/
private String jobName;
/**因为有分库,记录执行计划所属的数据库*/
private String dbDataSource;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getEnterpriseId() {
return enterpriseId;
}
public void setEnterpriseId(Long enterpriseId) {
this.enterpriseId = enterpriseId;
}
public Long getOrganizationId() {
return organizationId;
}
public void setOrganizationId(Long organizationId) {
this.organizationId = organizationId;
}
public Long getPlanId() {
return planId;
}
public void setPlanId(Long planId) {
this.planId = planId;
}
public String getPlanCorn() {
return planCorn;
}
public void setPlanCorn(String planCorn) {
this.planCorn = planCorn;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public String getDbDataSource() {
return dbDataSource;
}
public void setDbDataSource(String dbDataSource) {
this.dbDataSource = dbDataSource;
}
public Integer getIsWork() {
return isWork;
}
public void setIsWork(Integer isWork) {
this.isWork = isWork;
}
}
创建刷新quartz数据库中的任务接口:
package com.test.quartz.service;
public interface ScheduleTriggerService {
public void refreshTrigger();
public void refreshTrigger(CalcPlanQuartz plan);
}
接口实现
package com.test.quartz.task;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.newsee.charge.dao.CalcPlanQuartzMapper;
import com.newsee.charge.entity.CalcPlanQuartz;
import com.newsee.charge.service.ScheduleTriggerService;
@Service
public class ScheduleTriggerServiceImpl implements ScheduleTriggerService {
/** logger-logback */
private static final Logger logger = LoggerFactory.getLogger(ScheduleTriggerServiceImpl.class);
@Autowired
private Scheduler scheduler;
@Autowired
private CalcPlanQuartzMapper calcPlanQuartzMapper;
@Override
public void refreshTrigger() {
try {
// 查询出数据库中所有的定时任务
List planList = calcPlanQuartzMapper.selectAllPlan();
if (!CollectionUtils.isEmpty(planList)) {
for (ChargeCalcPlanQuartz plan : planList) {
refreshTrigger(plan);
}
}
} catch (Exception e) {
logger.error("定时任务每日刷新触发器任务异常,在ScheduleTriggerServiceImpl的方法refreshTrigger中,异常信息:", e);
}
}
@SuppressWarnings("unchecked")
@Override
public void refreshTrigger(CalcPlanQuartz plan) {
try {
// 查询出数据库中所有的定时任务
// 查看该任务是否为启用状态
Integer status = plan.getIsWork();
String name = plan.getPlanId().toString();
String group = plan.getJobName();
TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 说明本条任务还没有添加到quartz中
if (null == trigger) {
// 停用状态下不创建触发器
if (status.intValue() == 0) {
return;
}
JobDetail jobDetail = null;
try {
// 创建JobDetail(数据库中job_name存的任务全路径,这里就可以动态的把任务注入到JobDetail中)
jobDetail = JobBuilder.newJob((Class extends Job>) Class.forName(group)).withIdentity(name, group).build();
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(plan.getPlanCorn());
// 按新的cronExpression表达式构建一个新的trigger
trigger = TriggerBuilder.newTrigger().withIdentity(name, group).withSchedule(scheduleBuilder).build();
// 把trigger和jobDetail注入到调度器
scheduler.scheduleJob(jobDetail, trigger);
} catch (ClassNotFoundException e) {
logger.error("请检查jobName是否正确,jobName" + group + ":", e);
}
} else {
// 说明查出来的这条任务,已经设置到quartz中了
// Trigger已存在,先判断是否需要删除,如果不需要,再判定是否时间有变化
if (status.intValue() == 0) { // 如果是禁用,从quartz中删除这条任务
JobKey jobKey = JobKey.jobKey(name, group);
scheduler.deleteJob(jobKey);
return;
}
String searchCron = plan.getPlanCorn(); // 获取数据库的
String currentCron = trigger.getCronExpression();
if (!searchCron.equals(currentCron)) { // 说明该任务有变化,需要更新quartz中的对应的记录
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(searchCron);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
}
}
} catch (Exception e) {
logger.error("定时任务每日刷新触发器任务异常,在ScheduleTriggerServiceImpl的方法refreshTrigger中,异常信息:", e);
}
}
}
创建自定义的任务:
package com.test.quartz.task;
import com.test.common.utils.DateUtils;
import com.test.common.utils.SpringBeanUtils;
import com.test.database.util.DataSourceContextHolder;
import com.test.database.util.DataSourceTypeContextHolder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class ChargeCalcJob implements Job{
public void execute(JobExecutionContext context) throws JobExecutionException{
CronTrigger trigger = (CronTrigger)context.getTrigger();
String corn = trigger.getCronExpression();
String jobName = trigger.getKey().getName();
String group = trigger.getKey().getGroup();
PaymentCalcTaskServiceImpl calc = SpringBeanUtils.getBean(CalcTaskServiceImpl.class);
//业务中用了分库和读写分离,这里取第一个分库的写库
DataSourceTypeContextHolder.setWrite();
DataSourceContextHolder.setDB00();
//执行具体的业务操作
calc.executeAllAutoPlan();
}
}
我们可以建立个定时任务来定时刷新quartz中的任务调度信息:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.test.quartz.service.ScheduleTriggerService;
import com.test.database.util.DataSourceContextHolder;
import com.test.database.util.DataSourceTypeContextHolder;
@Component
public class PaymentCalcTask {
/** logger-logback */
private static final Logger logger = LoggerFactory.getLogger(PaymentCalcTask .class);
@Autowired
private ScheduleTriggerService scheduleTriggerService;
/**
* 每30秒扫描一次数据库,执行数据库中的定时任务
* 有分库,需要每个分库执行一次
*
* @throws Exception
*/
@Scheduled(cron = "0/30 * * * * ?")
public void paymentCacl() throws Exception {
logger.info("===↓↓↓↓↓===正在刷新定时任务===↓↓↓↓↓===");
Long start = System.currentTimeMillis();
DataSourceTypeContextHolder.setWrite();
DataSourceContextHolder.setDB00();
scheduleTriggerService.refreshTrigger();
Long cost = System.currentTimeMillis() - start;
logger.info("===↑↑↑↑↑===结束刷新定时任务,耗时:"+cost+"ms===↑↑↑↑↑===");
}
}
当然我们也可以调用scheduleTriggerService.refreshTrigger(CalcPlanQuartz plan)来精确更新quartz中具体某一条任务信息。