此案例在天降风云博主的基础上进行了补充
Quartz提供两种基本作业存储类型。第一种类型叫做RAMJobStore,第二种类型叫做JDBC作业存储。在默认情况下Quartz将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失
比如我们希望安排一个执行100次的任务,如果执行到50次时系统崩溃了,系统重启时任务的执行计数器将从0开始。
如果确实需要持久化任务调度信息,Quartz允许你通过调整其属性文件,将这些信息保存到数据库中。使用数据库保存任务调度信息后,即使系统崩溃后重新启动,任务的调度信息将得到恢复。如前面所说的例子,执行50次崩溃后重新运行,计数器将从51开始计数。使用了数据库保存信息的任务称为持久化任务。
本文实例代码: https://github.com/haoxiaoyong1014/springboot-quartz
此案例是基于springboot2版本,相关依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.0.5.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
接下来是application.yml配置文件信息,主要针对数据库和mybatis进行的配置
server:
port: 12741
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:mappers/*Mapper.xml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
name: sv
url: jdbc:mysql://115.29.32.62:3306/quartz?useUnicode=true&characterEncoding=utf8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
我们在 application.yml 同级目录下创建一个quartz.properties的文件,这个文件主要是对 Quartz 进行的配置,我把配置说明也写在了配置文件中,看着有点乱
# 固定前缀org.quartz
# 主要分为scheduler、threadPool、jobStore、plugin等部分
#
#
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
#如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true。
#在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做,'export '选项将被忽略
org.quartz.scheduler.rmi.export = false
#如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099。
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# threadCount和threadPriority将以setter的形式注入ThreadPool实例
# 并发个数 如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.
# 只有1到100之间的数字是非常实用的
org.quartz.threadPool.threadCount = 5
# 优先级 默认值为5
org.quartz.threadPool.threadPriority = 5
#可以是“true”或“false”,默认为false。
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)
org.quartz.jobStore.misfireThreshold = 5000
# 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失。
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作。
# StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
# 因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。
org.quartz.jobStore.useProperties=true
#表前缀
org.quartz.jobStore.tablePrefix = QRTZ_
#您需要设置JobStore应该使用哪个DataSource。
org.quartz.jobStore.dataSource = qzDS
org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL = jdbc:mysql://115.29.32.62:3306/quartz?useUnicode=true&characterEncoding=utf8
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = root
org.quartz.dataSource.qzDS.maxConnections = 10
#设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏。
#org.quartz.jobStore.isClustered=false
如果这里看着有点乱或者不明白可以参考: https://www.w3cschool.cn/quartz_doc/quartz_doc-i7oc2d9l.html
着我们在com.example.quartz下新建一个名为SchedulerConfig.java的文件。在这个文件里,对刚才我们新建的quartz.properties文件进行读取
@Configuration
public class SchedulerConfig {
@Bean(name="SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setQuartzProperties(quartzProperties());
return factory;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/**
* quartz初始化监听器
* 这个监听器可以监听到工程的启动,在工程停止再启动时可以让已有的定时任务继续进行。
* @return
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
/**
*
*通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean(name="Scheduler")
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
}
实现
先来看Job类。首先设置一个BaseJob接口,用来继承Job类:
public interface BaseJob extends Job {
public void execute(JobExecutionContext context) throws JobExecutionException;
}
然后两个 Job继承 BaseJob
HelloJob
public class HelloJob implements BaseJob{
private static Logger log = LoggerFactory.getLogger(HelloJob.class);
public HelloJob() {
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.info("Hello Job执行时间: " + new Date());
}
}
NewJob
public class NewJob implements BaseJob{
private static Logger log = LoggerFactory.getLogger(NewJob.class);
public NewJob() {
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.info("Hello Job执行时间: " + new Date());
}
}
接下来我们看下 controller类
@RestController
@RequestMapping(value = "job")
public class JobController {
@Autowired
private IJobAndTriggerService iJobAndTriggerService;
//加入Qulifier注解,通过名称注入bean
@Autowired
@Qualifier("Scheduler")
private Scheduler scheduler;
@Autowired
private DateUnit dateUnit;
private static Logger log = LoggerFactory.getLogger(JobController.class);
/**
* 添加任务
*
* @param jobInfo
* @throws Exception
*/
@PostMapping(value = "/addjob")
public void addjob(@RequestBody JobInfo jobInfo) throws Exception {
if ("".equals(jobInfo.getJobClassName()) || "".equals(jobInfo.getJobGroupName()) || "".equals(jobInfo.getCronExpression())) {
return;
}
if (jobInfo.getTimeType() == null) {
addCronJob(jobInfo);
return;
}
addSimpleJob(jobInfo);
}
//CronTrigger
public void addCronJob(JobInfo jobInfo) throws Exception {
// 启动调度器
scheduler.start();
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobInfo.getJobClassName()).getClass()).
withIdentity(jobInfo.getJobClassName(), jobInfo.getJobGroupName())
.build();
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getCronExpression());
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().
withIdentity(jobInfo.getJobClassName(), jobInfo.getJobGroupName())
.withSchedule(scheduleBuilder)
.build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
System.out.println("创建定时任务失败" + e);
throw new Exception("创建定时任务失败");
}
}
//Simple Trigger
public void addSimpleJob(JobInfo jobInfo) throws Exception {
// 启动调度器
scheduler.start();
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobInfo.getJobClassName()).getClass())
.withIdentity(jobInfo.getJobClassName(), jobInfo.getJobGroupName())
.build();
DateBuilder.IntervalUnit verDate = dateUnit.verification(jobInfo.getTimeType());
SimpleTrigger simpleTrigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity(jobInfo.getJobClassName(), jobInfo.getJobGroupName())
.startAt(futureDate(Integer.parseInt(jobInfo.getCronExpression()), verDate))
.forJob(jobInfo.getJobClassName(), jobInfo.getJobGroupName())
.build();
try {
scheduler.scheduleJob(jobDetail, simpleTrigger);
} catch (SchedulerException e) {
System.out.println("创建定时任务失败" + e);
throw new Exception("创建定时任务失败");
}
}
/**
* 暂停任务
*
* @param jobClassName
* @param jobGroupName
* @throws Exception
*/
@PostMapping(value = "/pausejob")
public void pausejob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) throws Exception {
jobPause(jobClassName, jobGroupName);
}
public void jobPause(String jobClassName, String jobGroupName) throws Exception {
scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
}
/**
* 恢复任务
*
* @param jobClassName
* @param jobGroupName
* @throws Exception
*/
@PostMapping(value = "/resumejob")
public void resumejob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) throws Exception {
jobresume(jobClassName, jobGroupName);
}
public void jobresume(String jobClassName, String jobGroupName) throws Exception {
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
}
/**
* 更新任务
*
* @param jobClassName
* @param jobGroupName
* @param cronExpression
* @throws Exception
*/
@PostMapping(value = "/reschedulejob")
public void rescheduleJob(@RequestParam(value = "jobClassName") String jobClassName,
@RequestParam(value = "jobGroupName") String jobGroupName,
@RequestParam(value = "cronExpression") String cronExpression) throws Exception {
jobreschedule(jobClassName, jobGroupName, cronExpression);
}
public void jobreschedule(String jobClassName, String jobGroupName, String cronExpression) throws Exception {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务失败" + e);
throw new Exception("更新定时任务失败");
}
}
/**
* 删除任务
* 删除操作前应该暂停该任务的触发器,并且停止该任务的执行
*
* @param jobClassName
* @param jobGroupName
* @throws Exception
*/
@PostMapping(value = "/deletejob")
public void deletejob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) throws Exception {
jobdelete(jobClassName, jobGroupName);
}
public void jobdelete(String jobClassName, String jobGroupName) throws Exception {
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
}
/**
* 查询任务
*
* @param pageNum
* @param pageSize
* @return
*/
@GetMapping(value = "/queryjob")
public Map<String, Object> queryjob(@RequestParam(value = "pageNum") Integer pageNum, @RequestParam(value = "pageSize") Integer pageSize) {
PageInfo<JobAndTrigger> jobAndTrigger = iJobAndTriggerService.getJobAndTriggerDetails(pageNum, pageSize);
Map<String, Object> map = new HashMap<String, Object>();
map.put("JobAndTrigger", jobAndTrigger);
map.put("number", jobAndTrigger.getTotal());
return map;
}
/**
* 根据类名称,通过反射得到该类,然后创建一个BaseJob的实例。
* 由于NewJob和HelloJob都实现了BaseJob,
* 所以这里不需要我们手动去判断。这里涉及到了一些java多态调用的机制
*
* @param classname
* @return
* @throws Exception
*/
public static BaseJob getClass(String classname) throws Exception {
Class<?> class1 = Class.forName(classname);
return (BaseJob) class1.newInstance();
}
}
你会看到addCronJob(jobInfo)
和addSimpleJob(jobInfo)
这两个方法,这就涉及到Simple Trigger和CronTrigger的区别
Simple Trigger:
SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。
根据描述,你可能已经发现了,SimpleTrigger的属性包括:开始时间、结束时间、重复次数以及重复的间隔。这些属性的含义与你所期望的是一致的,只是关于结束时间有一些地方需要注意。
重复次数,可以是0、正整数,以及常量SimpleTrigger.REPEAT_INDEFINITELY。重复的间隔,必须是0,或者long型的正数,表示毫秒。注意,如果重复间隔为0,trigger将会以重复次数并发执行(或者以scheduler可以处理的近似并发数)。
如果你还不熟悉DateBuilder,了解后你会发现使用它可以非常方便地构造基于开始时间(或终止时间)的调度策略。
endTime属性的值会覆盖设置重复次数的属性值;比如,你可以创建一个trigger,在终止时间之前每隔10秒执行一次,你不需要去计算在开始时间和终止时间之间的重复次数,只需要设置终止时间并将重复次数设置为REPEAT_INDEFINITELY(当然,你也可以将重复次数设置为一个很大的值,并保证该值比trigger在终止时间之前实际触发的次数要大即可)。
更多参见: https://www.w3cschool.cn/quartz_doc/quartz_doc-67a52d1f.html
CronTrigger
CronTrigger通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。
使用CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。
即使如此,和SimpleTrigger一样,CronTrigger有一个startTime,它指定何时生效,以及一个(可选的)endTime,用于指定何时停止计划。
Cron Expressions
Cron-Expressions用于配置CronTrigger的实例。Cron Expressions是由七个子表达式组成的字符串,用于描述日程表的各个细节。这些子表达式用空格分隔,并表示:
Seconds
Minutes
Hours
Day-of-Month
Month
Day-of-Week
Year (optional field)
一个完整的Cron-Expressions的例子是字符串“0 0 12?* WED“ - 这意味着”每个星期三下午12:00“。
Cron Expressions示例
CronTrigger示例1 - 创建一个触发器的表达式,每5分钟就会触发一次
“0 0/5 * * *?”
CronTrigger示例2 - 创建触发器的表达式,每5分钟触发一次,分钟后10秒(即上午10时10分,上午10:05:10等)。
“10 0/5 * * ?”
CronTrigger示例3 - 在每个星期三和星期五的10:30,11:30,12:30和13:30创建触发器的表达式。
“0 30 10-13? WED,FRI“
CronTrigger示例4 - 创建触发器的表达式,每个月5日和20日上午8点至10点之间每半小时触发一次。请注意,触发器将不会在上午10点开始,仅在8:00,8:30,9:00和9:30
“0 0/30 8-9 5,20 *?”
请注意,一些调度要求太复杂,无法用单一触发表示 - 例如“每上午9:00至10:00之间每5分钟,下午1:00至晚上10点之间每20分钟”一次。在这种情况下的解决方案是简单地创建两个触发器,并注册它们来运行相同的作业。
表达式
你可以到 这里自动生成
更多参见: https://www.w3cschool.cn/quartz_doc/quartz_doc-lwuv2d2a.html
在 controller类中你会看到最后有这么一个方法:
public static BaseJob getClass(String classname) throws Exception {
Class<?> class1 = Class.forName(classname);
return (BaseJob) class1.newInstance();
}
注意这个方法,根据类名称,通过反射得到该类,然后创建一个BaseJob的实例。由于NewJob和HelloJob都实现了BaseJob,所以这里不需要我们手动去判断。这里涉及到了一些java多态调用的机制,
其他设计到的 dao层以及service层,工具类就不在这里展示了,因为篇幅有长,案例代码在我的github上.
我们可以看到static下的JobManager.html,这个是前端的一个简单的管理页面。Spring Boot的web工程中,静态页面可以放在static目录下。这里贴一下主要代码,完整代码在github上.
//从服务器读取数据
loadData: function(pageNum, pageSize){
this.$http.get('job/queryjob?' + 'pageNum=' + pageNum + '&pageSize=' + pageSize).then(function(res){
console.log(res)
this.tableData = res.body.JobAndTrigger.list;
this.totalCount = res.body.number;
},function(){
console.log('failed');
});
},
//单行删除
handleDelete: function(index, row) {
this.$http.post('job/deletejob',{"jobClassName":row.job_NAME,"jobGroupName":row.job_GROUP},{emulateJSON: true}).then(function(res){
this.loadData( this.currentPage, this.pagesize);
},function(){
console.log('failed');
});
},
//暂停任务
handlePause: function(index, row){
this.$http.post('job/pausejob',{"jobClassName":row.job_NAME,"jobGroupName":row.job_GROUP},{emulateJSON: true}).then(function(res){
this.loadData( this.currentPage, this.pagesize);
},function(){
console.log('failed');
});
},
//恢复任务
handleResume: function(index, row){
this.$http.post('job/resumejob',{"jobClassName":row.job_NAME,"jobGroupName":row.job_GROUP},{emulateJSON: true}).then(function(res){
this.loadData( this.currentPage, this.pagesize);
},function(){
console.log('failed');
});
},
//搜索
search: function(){
this.loadData(this.currentPage, this.pagesize);
},
//弹出对话框
handleadd: function(){
this.checkboxChange = true;
},
change: function(){
this.dialogFormVisibleChange = true;
},
//添加
add: function(){
this.$http.post('job/addjob',{"jobClassName":this.form.jobName,"jobGroupName":this.form.jobGroup,"cronExpression":this.form.cronExpression}).then(function(res){
this.loadData(this.currentPage, this.pagesize);
this.dialogFormVisibleChange = false;
this.checkboxChange = false;
},function(){
console.log('failed');
});
},
addSimTir: function () {
console.log(this.value4)
this.$http.post('job/addjob',{"jobClassName":this.form.jobName,"jobGroupName":this.form.jobGroup,"cronExpression":this.form.cronExpression,
"timeType":this.value4}).then(function(res){
this.loadData(this.currentPage, this.pagesize);
this.dialogFormVisibleChange = false;
this.checkboxChange = false;
},function(){
console.log('failed');
});
},
//更新
handleUpdate: function(index, row){
console.log(row)
this.updateFormVisible = true;
this.updateform.jobName = row.job_CLASS_NAME;
this.updateform.jobGroup = row.job_GROUP;
},
//更新任务
update: function(){
this.$http.post
('job/reschedulejob',
{"jobClassName":this.updateform.jobName,
"jobGroupName":this.updateform.jobGroup,
"cronExpression":this.updateform.cronExpression
},{emulateJSON: true}
).then(function(res){
this.loadData(this.currentPage, this.pagesize);
this.updateFormVisible = false;
},function(){
console.log('failed');
});
},
//每页显示数据量变更
handleSizeChange: function(val) {
this.pagesize = val;
this.loadData(this.currentPage, this.pagesize);
},
//页码变更
handleCurrentChange: function(val) {
this.currentPage = val;
this.loadData(this.currentPage, this.pagesize);
},
},
});
//载入数据
vue.loadData(vue.currentPage, vue.pagesize);
最终展示效果:
-
启动项目
-
输入 http://localhost:12741/JobManager.html 会看到这个页面,在这里可以看到你的所有任务
- 添加任务
在添加任务的时候 任务名称
指定类名即可,通过反射得到该类, 由于NewJob和HelloJob都实现了BaseJob,
所以这里不需要我们手动去判断。这里涉及到了一些java多态调用的机制
任务名称
例如: HelloJob
当然持久化也是将任务添加到数据库的,sql脚本
在项目的跟目录下 quartz (官方提供的sql脚本),
在添加任务中的表达式
你可以到 这里自动生成
增加 SimpleTrigger
点击确定:
更新:
注意: 如果你当前 springboot版本是 2.1.x,就把quartz.properties
文件中的org.quartz.scheduler.instanceName = DefaultQuartzScheduler
注释掉;
org.quartz.scheduler.instanceName
Can be any string, and the value has no meaning to the scheduler itself - but rather serves as a mechanism for client code to distinguish schedulers when multiple instances are used within the same program. If you are using the clustering features, you must use the same name for every instance in the cluster that is ‘logically’ the same Scheduler.
可以是任何字符串,并且该值对调度程序本身没有意义 - 而是作为客户端代码在同一程序中使用多个实例时区分调度程序的机制。如果使用群集功能,则必须对群集中“逻辑上”为同一个调度程序的每个实例使用相同的名称。
如果你是在集群模式下就把SchedulerConfig
配置类中的下面这段代码注释掉:
/**
* quartz初始化监听器
* 这个监听器可以监听到工程的启动,在工程停止再启动时可以让已有的定时任务继续进行。
* @return
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
2019/9/1 更新:
我看博客中有人评论说使用SimpleTrigger
类型时存库不成功,在这里再统一说明一下,在使用SimpleTrigger
时,要想看到效果就要把时间设置的长一些,
因为定时任务执行完之后就会自动删除数据库中的记录;
在这里我也更正一下我的 sql 语句,之所以没有查出来SimpleTrigger
类型的数据是因为我的 sql,表连接是用了INNER JOIN
;在这里改为LEFT JOIN
就可以了;这样两种类型的
都可以查询出来了;以下是我更新之后的 sql 语句;
SELECT DISTINCT
QRTZ_JOB_DETAILS.JOB_NAME ,
QRTZ_JOB_DETAILS.JOB_GROUP ,
QRTZ_JOB_DETAILS.JOB_CLASS_NAME ,
QRTZ_TRIGGERS.TRIGGER_NAME ,
QRTZ_TRIGGERS.TRIGGER_GROUP ,
QRTZ_CRON_TRIGGERS.CRON_EXPRESSION ,
QRTZ_CRON_TRIGGERS.TIME_ZONE_ID
FROM
QRTZ_JOB_DETAILS
LEFT JOIN QRTZ_TRIGGERS ON QRTZ_TRIGGERS.TRIGGER_GROUP = QRTZ_JOB_DETAILS.JOB_GROUP
LEFT JOIN QRTZ_CRON_TRIGGERS ON QRTZ_JOB_DETAILS.JOB_NAME = QRTZ_TRIGGERS.JOB_NAME
AND QRTZ_TRIGGERS.TRIGGER_NAME = QRTZ_CRON_TRIGGERS.TRIGGER_NAME
AND QRTZ_TRIGGERS.TRIGGER_GROUP = QRTZ_CRON_TRIGGERS.TRIGGER_GROUP
2019-12-24更新
有评论说,在jobs里面@Autowired 注入service为null,今天主要解决这个问题,在JobController中弃用了反射,在tool包中加入了
SpringUtil工具类;主要目的是从Bean容器中获取指定的Bean;
JobController
public BaseJob getClass(String classname) throws Exception {
//Class<?> class1 = Class.forName(classname);
//BaseJob baseJob = (BaseJob) class1.newInstance();
BaseJob baseJob = (BaseJob) SpringUtil.getBean(classname);
return baseJob;
}
注意: 参数classnam是前端传过来的任务名称
,注意这里的任务名称要和具体的Job类上的@Component注解中value值相同;
例如:
@Component("helloJob")
public class HelloJob implements BaseJob {
private static Logger log = LoggerFactory.getLogger(HelloJob.class);
public HelloJob() {
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
IJobAndTriggerService iJobAndTriggerService = (IJobAndTriggerService) SpringUtil.getBean("IJobAndTriggerServiceImpl");
PageInfo<JobAndTrigger> jobAndTriggerDetails = iJobAndTriggerService.getJobAndTriggerDetails(1, 10);
System.out.println(jobAndTriggerDetails.getTotal());
log.info("Hello Job执行时间: " + new Date());
}
}
这里是首字母小写;所以在前端的任务名称
也要首字母小写;
在上面的HelloJob中,使用SpringUtil工具类来获取具体的Service Bean;
参考:
https://blog.csdn.net/u012907049/article/details/73801122
https://www.w3cschool.cn/quartz_doc/quartz_doc-1xbu2clr.html
写在最后