Quartz是什么?Quartz能干什么?
Quartz是一个任务调度的框架。它可以是你设置的某个时间点执行你设置的任务。
如果应用程序需要在给定时间执行任务,或者系统有需要连续维护作业,那么Quartz是理想的解决方案
任务持久化:
Quartz入门案例:
任务类:
/**
* 执行器,即需要定时执行的“某件事”
*/
public class HolleJob implements Job {
/**
* 执行事件的方法
* @param jobContext
* @throws JobExecutionException
*/
@Override
public void execute(JobExecutionContext jobContext) throws JobExecutionException {
//创建工作详情
JobDetail jobDetail=jobContext.getJobDetail();
//获取工作的名称
String name=jobDetail.getKey().getName();//任务名
String group=jobDetail.getKey().getGroup();//任务组名称
String jobData=jobDetail.getJobDataMap().getString("data04");//任务中的数据
System.out.println("job执行,job名称:"+name+" group:"+group+" data:"+jobData+new Date());
}
}
测试类:
public class test {
public static void main(String[] args) {
try {
//创建scheduler ,调度器
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();
//定义一个Trigger,触发条件类
TriggerBuilder triggerBuilder=TriggerBuilder.newTrigger();
triggerBuilder.withIdentity("trigger1","group1")//定义name/group
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2)//每隔2秒执行一次
.repeatForever()); //一直执行
//不设置,则会一直执行
triggerBuilder.endAt(new GregorianCalendar(2020,3,31,17,40,40).getTime());
Trigger trigger = triggerBuilder.build();
//定义一个JobDetail
//定义JOb类为HelloQuartz类,这是真正的执行逻辑所在
JobDetail jobDetail=JobBuilder.newJob(HolleJob.class)
.withIdentity("测试任务1","test") //定义name/group
.usingJobData("data04","jobData_ping")//定义属性,存储数据
.build();
//调度器,中加入 任务和触发器
scheduler.scheduleJob(jobDetail,trigger);
//启动任务调度
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
小结:一个简单quartz任务,是由调度器(scheduler),触发器(trigger),任务执行类 组成。 一个触发器绑定一个JobDetail,最后交给调度器进行调度,启动。
配置调度器:在工程创建一个quartz.properties文件
#指定调度器名称,非实现类
org.quartz.scheduler.instanceName=DefaultQuartzScheduler04
#指定线程池实现类
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#线程池线程数量
org.quartz.threadPool.threadCount= 10;
#优先级,默认5
org.quartz.threadPool.threadPriority= 5
# 非持久化job
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
核心类说明:
Scheduler:调度器。所有的调度都是由它控制的
scheduler就是Quartz的大脑,所有任务都是由它来设置
Scheduler包含两个重要组件:JobStore、ThreadPool
JobStore是会来存储运行时信息的,包括Trigger,Scheduler,JobDetail,业务锁等
ThreadPool就是线程池,Quartz有自己的线程池实现,所有傻的都会由线程池执行
SchedulerFactory,就是来创建Scheduler的,有两种实现:DirectSchedulerFactory和StdSchedulerFactory,前者可用来在代码时定制你自己的Schueduler参数,后者是直接读取classpath下的quartz.properties(没有则使用默认值)来创建scheduler,通常来讲,我们,我们使用StdSchedulerFactory也就足够了。
Trigger(触发器)
SimpleTrigger:指定从某一个时间开始,以一定的时间间隔执行任务
CalendarIntervalTrigger:类似SimpleTrigger,不同的是:SimpleTrigger不能指定每隔一个月执行一次,CalendarIntervalTrigger可以
DailyTimeIntervalTrigger:指定每天的某个时间段内,以一定的时间间隔执行任务,并且这可以支持指定星期
CronTrigger(最常用的触发器):适合于更复杂的任务,它支持类型于Linux Crom的语法,基本它拥有以上三个触发器的绝大部分能力,它的属性只有Cron表达式。
cron表达式:
位置 时间域 允许值 特殊值
1 秒 0~59 ,- * /
2 分钟 0~59 ,- * /
3 小时 0~23 ,- * /
4 日期 1~31 ,- * / ? L W
5 月份 1~12 ,- * /
6 星期 1~7 ,- * / ? L
7 年份 1970-2099 ,- * /
特殊值:
(*):可用在所有字段中,表示对应时间域的每个时刻
(?):该字符只能日期和星期字段中使用,它通常指定为“不确定值”(星期和日期不能同时出现,必有一个为?)
( - ) : 表达一个范围,如在小时字段中使用8-12,则表示10到12点
( , ):表达一个列表值,如在小时中使用4,6,8 则表示4小时,6小时,8小时
( / ):表达一个步进序列,如x/y ,x为起始值,y为步长值。
( L ):表达Last的意思,在日期中使用,则表示指定月份的最后一日。在星期中使用,则表示星期六,如果前面有一个数值,则这个月的最后一个星期几
( W ): 只能在日期字段里,表示离当前月该日期最近的工作日
( LW ):在日期字段可以组合使用LW,表示当月的最后一个工作日
example:
public class test {
public static void main(String[] args) {
cronTriggerMother();
}
public static void cronTriggerMother(){
try {
//创建scheduler ,调度器
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();
//定义一个Trigger,触发条件类
TriggerBuilder triggerBuilder=TriggerBuilder.newTrigger();
triggerBuilder.withIdentity("trigger1","group1")//定义name/group
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?")); //每5秒执行一次
Trigger trigger = triggerBuilder.build();
//定义一个JobDetail
//定义JOb类为HelloQuartz类,这是真正的执行逻辑所在
JobDetail jobDetail=JobBuilder.newJob(HolleJob.class)
.withIdentity("测试任务1","test") //定义name/group
.usingJobData("data04","jobData_ping")//定义属性,存储数据
.build();
//调度器,中加入 任务和触发器
scheduler.scheduleJob(jobDetail,trigger);
//启动任务调度
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
Job并发:job是有可能 并发执行的,比如一个任务要执行10中,而调度算法是每五秒触发一次,那么就有可能多个任务被并发执行。
有时候我们并不希望任务并发执行。这时候一个@DisallowConcurrentExecution就可以解决这个问题
@DisallowConcurrentExecution
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext jobContext) throws JobExecutionException {
System.out.println("job执行");
}
}
注意:@DisallowConcurrentExecution是对JobDetail实例生效的,也就是如果你定义两个JobDatail,引用同一个job类,是可以并发执行的
Spring整合Quartz
依赖:
org.springframework
spring-webmvc
${spring.version}
mysql
mysql-connector-java
${mysql.version}
org.springframework
spring-jdbc
${spring.version}
org.projectlombok
lombok
${lombok.version}
com.alibaba
druid
${druid.version}
org.quartz-scheduler
quartz
${quartz.version}
org.springframework
spring-tx
${spring.version}
org.springframework
spring-context-support
${spring.version}
org.springframework
spring-web
${spring.version}
配置:
调度器 SchedulerFacrotyBean
触发器 CronTriggerFactoryBean
JobDetail JobDetailFactoryBean
web项目的全局配置:
Archetype Created Web Application
contextConfigLocation
classpath:applicationContext.xml
org.springframework.web.context.ContextLoaderListener
测试:
public class test {
public static void main(String[] args) throws Exception {
springAndQuartzTest();
}
public static void springAndQuartzTest() throws Exception {
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("======================================");
StdScheduler scheduler = (StdScheduler) context.getBean("scheduler");
System.out.println(scheduler.getClass());
Thread.sleep(30000);
//如下api可以设计几个Controller,用来做job的暂停和删除
/*scheduler.pauseTrigger(TriggerKey.triggerKey("trigger1","group1"));//暂停触发器
scheduler.unscheduleJob(TriggerKey.triggerKey("trigger1","group1"));//移除触发器
scheduler.deleteJob(JobKey.jobKey("job1","group1"));//删除任务*/
/* scheduler.pauseJob(new JobKey("job1","group1"));//暂停任务
Thread.sleep(10000);
scheduler.resumeJob(new JobKey("job1","group1"));//恢复任务*/
/* Thread.sleep(10000);
GroupMatcher group1=GroupMatcher.groupEquals("group1");//匹配任务组为group1
scheduler.pauseJobs(group1);//暂停任务组中的所有任务
Thread.sleep(3000);
scheduler.resumeJobs(group1);//恢复任务组的所有任务*/
}
}
持久化:
建表(mysql数据库的建表SQL,使用其它数据库可以去官网下载,然后打开quartz-2.2.3\docs\dbTables ,选择对应的数据库):
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
#
# By: Ron Cordell - roncordell
# I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.
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(200) NOT NULL,
JOB_GROUP VARCHAR(200) 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))
ENGINE=INNODB;
CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) 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(200) 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))
ENGINE=INNODB;
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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))
ENGINE=INNODB;
CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) 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))
ENGINE=INNODB;
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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))
ENGINE=INNODB;
CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=INNODB;
CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=INNODB;
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=INNODB;
CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) 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(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=INNODB;
CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=INNODB;
CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=INNODB;
CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
COMMIT;
进行配置(springApplication.xml):
#指定调度器名称,非实现类
org.quartz.scheduler.instanceName=MyScheduler
#指定线程池实现类
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#线程池线程数量
org.quartz.threadPool.threadCount= 10
#优先级,默认5
org.quartz.threadPool.threadPriority= 5
# 默认存储在内存中
#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
#持久化
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#quartz表的前缀
org.quartz.jobStore.tablePrefix=QRTZ_
配置springmvc.xml
配置web.xml
Archetype Created Web Application
springmvcDispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
springmvcDispatcherServlet
/
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
characterEncodingFilter
/*
index.jsp
add.jsp
contextConfigLocation
classpath:applicationContext_sql.xml
org.springframework.web.context.ContextLoaderListener
创建一个pojo
package cn.ping.quartz.pojo;
import lombok.Data;
@Data
public class JobAndTrigger {
private String jobName;
private String jobGroup;
private String jobClassName;
private String triggerName;
private String triggerGroup;
private Long repeatInterval;
private Long timesTriggered;
private String cronExpression;
private String timeZoneId;
}
创建一个controller
package cn.ping.quartz.controller;
import cn.ping.quartz.pojo.JobAndTrigger;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("quartz")
public class QuartzController {
@Autowired//注入了工厂中 调度器
private Scheduler scheduler;
/**
* 添加一个定时任务
*
* @param jt 新任务的相关数据
* @return
*/
@RequestMapping("add")
public String add(JobAndTrigger jt) throws ClassNotFoundException, SchedulerException {
//创建JobDetail
JobDetail jobDetail = JobBuilder.newJob((Class extends Job>) Class.forName(jt.getJobClassName()))
.withIdentity(jt.getJobName(), jt.getJobGroup()).storeDurably(true)
.build();
//创建CronTrigger
CronTrigger cronTrigger= TriggerBuilder.newTrigger().withIdentity(jt.getJobName(),jt.getJobGroup())
.withSchedule(CronScheduleBuilder.cronSchedule(jt.getCronExpression()))
.build();
scheduler.scheduleJob(jobDetail,cronTrigger);
scheduler.start();
return "ok";
}
}
修改index.jsp
运行,添加数据测试,最后在数据库
SELECT * FROM QRTZ_JOB_DETAILS
会发现有一条记录,同时添加的定时任务也在执行