《从零打造项目》系列文章

工具

  • 比MyBatis Generator更强大的代码生成器

ORM框架选型

  • SpringBoot项目基础设施搭建
  • SpringBoot集成Mybatis项目实操
  • SpringBoot集成MybatisPlus项目实操
  • SpringBoot集成Spring Data JPA项目实操

数据库变更管理

  • 数据库变更管理:Liquibase or Flyway

  • SpringBoot结合Liquibase实现数据库变更管理

定时任务框架

  • Java定时任务技术分析
  • SpringBoot结合Quartz实现定时任务

缓存

  • 待更新

安全框架

  • 待更新

开发规范

  • 待更新

前言

需求

假设我们有这样两个需求:

1、用户注册1分钟后给用户发送欢迎通知。

2、每天8点钟给用户发送当天温度通知。

接下来我们就准备实现上述两个需求,关于通知发送就只是简单地控制台输出,没有真正实现该功能。

关于定时任务框架的选择,本文将选用 Quartz 来实现上述需求,下面简单介绍一下 Quartz。

Quartz介绍

Quartz 作为一个优秀的开源调度框架,Quartz 具有以下特点:

  1. 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
  2. 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
  3. 分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。

另外,作为 Spring 默认的调度框架,Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。

在 Quartz 体系结构中,有三个组件非常重要:

  • Scheduler :调度器。Scheduler启动Trigger去执行Job。
  • Trigger :触发器。用来定义 Job(任务)触发条件、触发时间,触发间隔,终止时间等。四大类型:SimpleTrigger(简单的触发器)、CornTrigger(Cron表达式触发器)、DateIntervalTrigger(日期触发器)、CalendarIntervalTrigger(日历触发器)。
  • Job :任务。具体要执行的业务逻辑,比如:发送短信、发送邮件、访问数据库、同步数据等。

Quartz集群

Quartz 的存储方式有两种:RAMJobStoreJDBCJobStore。从名字就能看出,存在内存中和存在数据库当中。在默认情况下Quartz将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失。

两者之间的区别如下图所示:

JDBCJobStore 存储可以实现 Quartz 集群模式,实际场景下,我们必然需要考虑定时任务的高可用,即选用集群模式。

Quartz 集群架构如下,集群中的每个节点是一个独立的 Quartz 应用,且独立的 Quartz 节点并不与另一节点通信,而是通过相同的数据库表来感知另一 Quartz 应用。简而言之,Quartz 应用、数据库支撑、多节点部署即可搭建起Quartz的应用集群。

**Quartz 集群共用同一个数据库,由数据库中的数据来确定任务是否正在执行,如果该任务正在执行,则其他服务器就不能去执行该调度任务。**Quartz集群的特点如下:

1、持久化

Quartz 可以将调度器 scheduler、触发器 trigger 以及任务 Job 的运行时信息存储至数据库中,采用 JDBCJobStore,如果服务器异常时,可以基于数据库中的存储信息进行任务恢复。

2、高可用性

如果相关服务器节点挂掉的话,集群的其他节点则会继续执行相关任务。

3、伸缩性

如果集群中的节点数过少,导致相关任务无法及时执行,可以增加额外的服务器节点,只需将其他节点上的脚本及配置信息拷贝至新部署的节点上运行即可。

4、负载均衡

Quartz 使用随机的负载均衡算法,任务 job 是以随机的方式由不同的节点上 Scheduler 实例来执行。但当前不存在一个方法指派一个Job到集群中的特定节点。

下面我们就使用 Quartz 来实现定时任务推送。

项目实践

创建一个 Maven 项目,名为 quartz-task。

环境配置

1、在 pom.xml 文件中,引入相关依赖。

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.3</version><relativePath/>
</parent><properties><java.version>1.8</java.version><fastjson.version>1.2.73</fastjson.version><hutool.version>5.5.1</hutool.version><mysql.version>8.0.19</mysql.version><org.mapstruct.version>1.4.2.Final</org.mapstruct.version><org.projectlombok.version>1.18.20</org.projectlombok.version><druid.version>1.1.18</druid.version><springdoc.version>1.6.9</springdoc.version><liquibase.version>4.16.1</liquibase.version>
</properties><dependencies><!-- 实现对 Spring MVC 的自动化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- 实现对 Quartz 的自动化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId><version>3.5.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><dependency><groupId>org.liquibase</groupId><artifactId>liquibase-core</artifactId><version>4.16.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.12</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${org.mapstruct.version}</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version></dependency><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-ui</artifactId><version>${springdoc.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.liquibase</groupId><artifactId>liquibase-maven-plugin</artifactId><version>4.16.1</version><configuration><!--properties文件路径,该文件记录了数据库连接信息等--><propertyFile>src/main/resources/application.yml</propertyFile><propertyFileWillOverride>true</propertyFileWillOverride></configuration></plugin><plugin><groupId>com.msdn.hresh</groupId><artifactId>liquibase-changelog-generate</artifactId><version>1.0-SNAPSHOT</version><configuration><sourceFolderPath>src/main/resources/liquibase/changelogs/</sourceFolderPath><!-- 当前应用根目录 --></configuration></plugin></plugins>
</build>

2、添加 application.yml

server:port: 8080spring:application:name: quartz-taskdatasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/quartz_test_db?serverTimezone=Hongkong&characterEncoding=utf-8&useSSL=falseusername: rootpassword: rootliquibase:enabled: truechange-log: classpath:liquibase/master.xml# 记录版本日志表database-change-log-table: databasechangelog# 记录版本改变lock表database-change-log-lock-table: databasechangeloglockquartz:# 程序结束时会等待quartz相关的内容结束wait-for-jobs-to-complete-on-shutdown: true# 将任务等保存化到数据库job-store-type: jdbc# QuartzScheduler启动时覆盖己存在的Joboverwrite-existing-jobs: falsemybatis:mapper-locations: classpath:mapper/*Mapper.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpllazy-loading-enabled: truechangeLogFile: src/main/resources/liquibase/master.xml

3、关于 Quartz 的配置,可以一并写在 application.yml 中,类似于这样:

spring:datasource:user:url: jdbc:mysql://127.0.0.1:3306/quartz_test_db?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword:quartz:url: jdbc:mysql://127.0.0.1:3306/quartz_test_db?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword:# Quartz 的配置,对应 QuartzProperties 配置类quartz:scheduler-name: clusteredScheduler # Scheduler 名字。默认为 schedulerNamejob-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 trueoverwrite-existing-jobs: false # 是否覆盖已有 Job 的配置properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档org:quartz:# JobStore 相关配置jobStore:# 数据源名称dataSource: quartzDataSource # 使用的数据源class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 实现类driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegatetablePrefix: QRTZ_ # Quartz 表前缀isClustered: true # 是集群模式clusterCheckinInterval: 1000useProperties: false# 线程池相关配置threadPool:threadCount: 25 # 线程池大小。默认为 10 。threadPriority: 5 # 线程优先级class: org.quartz.simpl.SimpleThreadPool # 线程池类型

不过因为 Quartz 配置内容过多,所以单独新建了 quartz.properties。

org.quartz.jobStore.useProperties=true#在集群中每个实例都必须有一个唯一的instanceId,但是应该有一个相同的instanceName【默认“QuartzScheduler”】【非必须】
org.quartz.scheduler.instanceName=quartzScheduler
# Scheduler实例ID,全局唯一
org.quartz.scheduler.instanceId=AUTO
# 指定scheduler的主线程是否为后台线程,【默认false】【非必须】
org.quartz.scheduler.makeSchedulerThreadDaemon=true
# 触发job时是否需要拥有锁
org.quartz.jobStore.acquireTriggersWithinLock = true#线程池配置
#线程池类型
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#线程池大小
org.quartz.threadPool.threadCount=10
#线程优先级
org.quartz.threadPool.threadPriority=5#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix=qrtz_
# 最大能忍受的触发超时时间(触发器被认定为“misfired”之前),如果超过则认为“失误”【默认60秒】
org.quartz.jobStore.misfireThreshold = 60000
# 配置数据源的名称,在后面配置数据源的时候要用到,
# 例如org.quartz.dataSource.myDS.driver=com.mysql.cj.jdbc.Driver
org.quartz.jobStore.dataSource = myDS# 集群配置
org.quartz.jobStore.isClustered = true
# 检入到数据库中的频率(毫秒)。检查是否其他的实例到了应当检入的时候未检入这能指出一个失败的实例,
# 且当前Scheduler会以此来接管执行失败并可恢复的Job通过检入操作,Scheduler也会更新自身的状态记录
org.quartz.jobStore.clusterCheckinInterval=5000
# jobStore处理未按时触发的Job的数量
org.quartz.jobStore.maxMisfiresToHandleAtATime=20# datasource
org.quartz.dataSource.myDS.provider = hikaricp
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.dataSource.myDS.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.URL=jdbc:mysql://localhost:3306/quartz_test_db?characterEncoding=utf8
org.quartz.dataSource.myDS.user=root
org.quartz.dataSource.myDS.password=root
# 最大连接数
org.quartz.dataSource.myDS.maxConnections = 10
# dataSource用于检测connection是否failed/corrupt的SQL语句
org.quartz.dataSource.myDS.validationQuery = select 1

关于 properties 文件中每个属性的含义,推荐阅读《Quartz配置文件详解&生产配置》。

4、手动在数据库中创建 Quartz 相关表,可以从 Quartz 发行版下载中找到tables_mysql.sql ,或直接从其源代码中找到 。因为我们使用 MySQL ,所以使用 tables_mysql_innodb.sql 脚本。

核心类

1、Quartz 配置类

@Configuration
public class SchedulerConfig {@Beanpublic SchedulerFactoryBean scheduler(DataSource dataSource) {SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));schedulerFactory.setDataSource(dataSource);schedulerFactory.setJobFactory(new SpringBeanJobFactory());schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");return schedulerFactory;}}

2、Quartz 相关实体类

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ScheduleTask {// 任务名private String jobName;// 任务组private String groupName;// 任务数据private String jobData;// 任务执行处理类,小写字母开头private String jobHandlerClass;// 任务执行时间private Long jobTime;// 任务执行时间,cron时间表达式 (如:0/5 * * * * ? )private String jobCronTime;// 任务执行次数,(<0:表示不限次数)private int jobTimes;}@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class JobResponse {// 任务名private String jobName;// 任务组private String groupName;// 任务数据private String jobData;private String triggerKey;private String jobStatus;// 任务执行时间,cron时间表达式 (如:0/5 * * * * ? )private String jobCronTime;}

3、QuartzService定时器操作

@Service
@RequiredArgsConstructor
public class QuartzTaskService {public static final String JOB_DATA_KEY = "jobData";public static final String JOB_HANDLER_CLASS_KEY = "jobHandlerClass";private final Scheduler scheduler;public void createJob(ScheduleTask task) throws SchedulerException {JobDetail jobDetail = JobBuilder.newJob().ofType(MessageJob.class).withIdentity(task.getJobName(), task.getGroupName()).usingJobData(JOB_DATA_KEY, task.getJobData()).usingJobData(JOB_HANDLER_CLASS_KEY, task.getJobHandlerClass()).build();Trigger trigger;if (StrUtil.isNotBlank(task.getJobCronTime())) {trigger = TriggerBuilder.newTrigger().forJob(jobDetail).withIdentity(task.getJobName() + "_trigger", task.getGroupName()).withSchedule(CronScheduleBuilder.cronSchedule(task.getJobCronTime())).build();} else {trigger = TriggerBuilder.newTrigger().forJob(jobDetail).withIdentity(task.getJobName() + "_trigger", task.getGroupName()).startAt(new Date(task.getJobTime())).build();}scheduler.scheduleJob(jobDetail, trigger);}// 修改 一个job的 时间表达式@SneakyThrowspublic void updateJob(String jobName, String jobGroupName, String jobTime) {TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).build();// 重启触发器scheduler.rescheduleJob(triggerKey, trigger);}@SneakyThrowspublic void removeTask(JobKey jobKey) {scheduler.deleteJob(jobKey);}// 暂停一个job@SneakyThrowspublic void pauseJob(JobKey jobKey) {scheduler.pauseJob(jobKey);}// 恢复一个job@SneakyThrowspublic void resumeJob(JobKey jobKey) {scheduler.resumeJob(jobKey);}// 立即执行一个job@SneakyThrowspublic void runJobNow(JobKey jobKey) {scheduler.triggerJob(jobKey);}// 获取所有计划中的任务列表public List<JobResponse> queryAllJob() throws SchedulerException {List<JobResponse> jobResponses = new ArrayList<>();GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);for (JobKey jobKey : jobKeys) {List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);for (Trigger trigger : triggers) {Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());JobResponse jobResponse = getJobResponse(jobKey, trigger, triggerState);jobResponses.add(jobResponse);}}return jobResponses;}private JobResponse getJobResponse(JobKey jobKey, Trigger trigger, TriggerState triggerState) {JobResponse jobResponse = JobResponse.builder().jobName(jobKey.getName()).groupName(jobKey.getGroup()).triggerKey(trigger.getKey().toString()).build();jobResponse.setJobStatus(triggerState.name());if (trigger instanceof CronTrigger) {CronTrigger cronTrigger = (CronTrigger) trigger;String cronExpression = cronTrigger.getCronExpression();jobResponse.setJobCronTime(cronExpression);}return jobResponse;}// 获取所有正在运行的jobpublic List<JobResponse> queryRunJob() throws SchedulerException {List<JobResponse> jobResponses = new ArrayList<>();List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();for (JobExecutionContext executingJob : executingJobs) {JobDetail jobDetail = executingJob.getJobDetail();JobKey jobKey = jobDetail.getKey();Trigger trigger = executingJob.getTrigger();Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());JobResponse jobResponse = getJobResponse(jobKey, trigger, triggerState);jobResponses.add(jobResponse);}return jobResponses;}
}

4、自定义 Job 类

@Setter
@Slf4j
public class MessageJob implements Job {private ApplicationContext applicationContext;private String jobData;private String jobHandlerClass;@SneakyThrows@Overridepublic void execute(JobExecutionContext context) {log.info("quartz job data: " + jobData + ", jobHandlerClass: " + jobHandlerClass);MessageHandler messageHandler = (MessageHandler) applicationContext.getBean(jobHandlerClass);messageHandler.handlerMessage(jobData);}
}

5、创建定时任务处理接口

public interface MessageHandler {void handlerMessage(String jobData) throws JsonProcessingException;}

不同的定时任务对应不同的任务处理类,即实现 MessageHandler 接口。

业务实现

1、UserService,包括用户注册,给用户发送欢迎消息,以及发送天气温度通知。

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {private final ScheduleTaskService scheduleTaskService;private final UserMapper userMapper;private final UserStruct userStruct;private final WeatherService weatherService;/*** 假设有这样一个业务需求,每当有新用户注册,则1分钟后会给用户发送欢迎通知.** @param userRequest 用户请求体*/public void register(UserRequest userRequest) {if (Objects.isNull(userRequest) || isBlank(userRequest.getUsername()) ||isBlank(userRequest.getPassword())) {BusinessException.fail("账号或密码为空!");}User user = userStruct.toUser(userRequest);userMapper.insert(user);scheduleTaskService.createTask(user.getUsername());}public void sayHelloToUser(String username) {User user = userMapper.selectByUserName(username);String message = "Welcome to Java,I am hresh.";log.info(user.getUsername() + " , hello, " + message);}public void pushWeatherNotification(List<User> users) {log.info("执行发送天气通知给用户的任务。。。");WeatherInfo weatherInfo = weatherService.getWeather(WeatherConstant.WU_HAN);for (User user : users) {log.info(user.getUsername() + "----" + weatherInfo.toString());}}
}

2、ScheduleTaskService 创建定时任务

@Service
@RequiredArgsConstructor
public class ScheduleTaskService {private final QuartzTaskService quartzTaskService;private final UserMapper userMapper;@SneakyThrowspublic void createTask(String username) {LocalDateTime scheduleTime = LocalDateTime.now().plusMinutes(1L);String jobName = "sayHello";String jogGroupName = "group1";String jobHandlerClass = "sayHelloHandler";ScheduleTask scheduleTask = ScheduleTask.builder().jobName(jobName).groupName(jogGroupName).jobData(new ObjectMapper().writeValueAsString(username)).jobHandlerClass(jobHandlerClass).jobTime(DateUtil.toEpochMilli(scheduleTime)).build();quartzTaskService.createJob(scheduleTask);}@SneakyThrowspublic void createWeatherNotificationTask(String jobTime) {String jobName = "weatherNotification";String jogGroupName = "group2";String jobHandlerClass = "weatherNotificationHandler";List<User> users = userMapper.queryAll();ScheduleTask scheduleTask = ScheduleTask.builder().jobName(jobName).groupName(jogGroupName).jobData(JSON.toJSONString(users)).jobHandlerClass(jobHandlerClass).jobCronTime(jobTime).build();quartzTaskService.createJob(scheduleTask);}}

3、WeatherService,获取天气温度等信息

@Service
@RequiredArgsConstructor
public class WeatherService {private final RestTemplate restTemplate;public WeatherInfo getWeather(WeatherConstant weatherConstant) {String json = restTemplate.getForObject("http://t.weather.sojson.com/api/weather/city/" + weatherConstant.getCode(), String.class);JSONObject jsonObject = JSONObject.parseObject(json);Integer status = jsonObject.getInteger("status");String currentDay = DateUtil.getDay(LocalDateTime.now());if (status == 200) {JSONObject data = jsonObject.getJSONObject("data");String quality = data.getString("quality");String notice = data.getString("ganmao");String currentTemperature = data.getString("wendu");JSONArray forecast = data.getJSONArray("forecast");JSONObject dayInfo = forecast.getJSONObject(0);String high = dayInfo.getString("high");String low = dayInfo.getString("low");String weather = dayInfo.getString("type");String windDirection = dayInfo.getString("fx");return WeatherInfo.builder().airQuality(quality + "污染").date(currentDay).cityName(weatherConstant.getCityName()).temperature(low + "-" + high).weather(weather).windDirection(windDirection).notice(notice).currentTemperature(currentTemperature).build();}return null;}}

4、UserController,对外暴露接口

@RestController
@RequiredArgsConstructor
public class UserController {private final UserService userService;private final ScheduleTaskService scheduleTaskService;@PostMapping("/register")public Result<Object> register(@RequestBody UserRequest userRequest) {userService.register(userRequest);return Result.ok();}@PostMapping("/weather-notification")public Result<Object> scheduledSayHello(@RequestParam("jobTime") String jobTime) {scheduleTaskService.createWeatherNotificationTask(jobTime);return Result.ok();}}

还有一些代码没有展示出来,感兴趣的朋友到时候可以去我的 Github 上看一下项目源码。

测试

为了演示效果,发送天气温度通知,我们暂时设为每2分钟一次。

首先通过 postman 来注册用户

可以到数据库中看一下 qrtz_job_details 表中的数据,如下所示:

等待一分钟后,控制台会输出如下内容:

执行完定时任务后,qrtz_job_details 表中相关数据也会被删除掉。

接着来测试发送天气通知

因为咱们测试的是每两分钟跑一次定时任务,所以 qrtz_job_details 表中会一直存在这么一条数据:

问题记录

1、初次启动定时任务报错

Couldn't acquire next trigger: Unknown column 'SCHED_TIME' in 'field list'

原因:我们下载的 SQL 文件有问题,在 qrtz_fired_triggers 表的构建语句中缺少 sched_time 字段,完整的 SQL 语句如下:

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

2、增加数据源配置过程中遇到的坑:

在 quartz.properties 文件中没有增加 org.quartz.dataSource.myDS.provider = hikaricp 配置时,启动一直报错:

Caused by: org.quartz.SchedulerException: Could not initialize DataSource: qzDS

Caused by: org.quartz.SchedulerException: ConnectionProvider class ‘org.quartz.utils.C3p0PoolingConnectionProvider’ could not be instantiated.

后来增加了 provider: hikaricp 这个配置,启动不报错。

总结

Quartz 框架出现的比较早,后续不少定时框架,或多或少都基于 Quartz 研发的,比如当当网的elastic-job就是基于quartz二次开发之后的分布式调度解决方案。

并且,Quartz 并没有内置 UI 管理控制台,不过你可以使用 quartzui 这个开源项目来解决这个问题。

虽然 Quartz 可以实现我们的需求,但代码入侵比较严重,使用起来比较麻烦,后续我们再研究一下其他的定时任务框架。

感兴趣的朋友可以去我的 Github 下载相关代码,如果对你有所帮助,不妨 Star 一下,谢谢大家支持!

参考文献

Quartz配置文件详解&生产配置

Quartz.NET Configuration Reference

springBoot整合Quartz定时任务(持久化到数据库,开箱即食)

Quartz学习总结之Job存储模式和集群

Quartz应用与集群原理分析

quartz (从原理到应用)详解篇(转)

SpringBoot结合Quartz实现定时任务相关推荐

  1. java quartz 动态执行,浅谈SpringBoot集成Quartz动态定时任务

    SpringBoot自带schedule 沿用的springboot少xml配置的优良传统,本身支持表达式等多种定时任务 注意在程序启动的时候加上@EnableScheduling @Schedule ...

  2. SpringBoot集成Quartz(定时任务)

    SpringBoot集成Quartz(定时任务)_鱼找水需要时间的博客-CSDN博客_springboot集成quartz

  3. 定时器 java qua_【spring-boot】 springboot整合quartz实现定时任务

    在做项目时有时候会有定时器任务的功能,比如某某时间应该做什么,多少秒应该怎么样之类的. spring支持多种定时任务的实现.我们来介绍下使用spring的定时器和使用quartz定时器 1.我们使用s ...

  4. SpringBoot集成Quartz动态定时任务

    SpringBoot自带schedule 沿用的springboot少xml配置的优良传统,本身支持表达式等多种定时任务 注意在程序启动的时候加上@EnableScheduling @Schedule ...

  5. SpringBoot集成Quartz实现定时任务的动态创建、启动、暂停、恢复、删除。

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:毅大师 blog.csdn.net/qq_39648 ...

  6. SpringBoot集成Quartz实现定时任务

    1.Quartz官网 http://www.quartz-scheduler.org/ Quartz是功能强大的开源作业调度库,几乎可以集成到任何Java应用程序中-从最小的独立应用程序到最大的电子商 ...

  7. Springboot整合quartz实现定时任务(基本配置)

    //定义相关配置 @Configuration @EnableScheduling public class QuartzConfig {@Autowiredprivate SpringJobFact ...

  8. 第四十章:基于SpringBoot Quartz完成定时任务分布式多节点负载持久化

    在上一章[第三十九章:基于SpringBoot & Quartz完成定时任务分布式单节点持久化]中我们已经完成了任务的持久化,当我们创建一个任务时任务会被quartz定时任务框架自动持久化到数 ...

  9. Spring Boot定时任务-SpringBoot整合Quartz

    如何通过SpringBoot整合Quartz框架,我们首先去创建一个项目,接下来我们需要在pom文件里添加坐标,我们在使用SpringBoot整合Quartz的时候,需要添加哪些坐标呢,我们来看一下, ...

最新文章

  1. DIV布局SEO的影响
  2. 算法 - 输出一个字符串的全排列(C++)
  3. 好像是第一次在公司外的论坛上公开演讲
  4. window下pip 用不了的一种解决办法
  5. 《算法导论》——MergeSort
  6. Leetcode--881. 救生艇
  7. mysql buffer 命中率_从MySQL的源码剖析Innodb buffer的命中率计算
  8. Go入门之——GOROOT、GOPATH、GOBIN 、Project目录
  9. nyist 2 括号配对问题
  10. python-获取当前文件名
  11. 微信小程序:宝宝起名神器
  12. 如何解决hangfire使用redis存储时,如果采用了prefix报“Key has MOVED from Endpoint”的错...
  13. android usb 投电视盒子,电脑还可以管理电视盒子?这样做就可以!
  14. 不能用float、double 存储金额——BigDecimal详解
  15. 神经网络权重是什么意思,神经网络权重调整方法
  16. 了解DN、RDN和CN
  17. 简单弄懂配置github CND加速
  18. 植物大战僵尸设计元素浅析
  19. 信息安全技术——(一)绪论
  20. 如何让php文件常驻内存,【后端开辟】Asf PHP开辟之设置信息常驻体系内存

热门文章

  1. Mybatis-plus尚硅谷跟学课堂笔记
  2. css 伪元素居中对齐
  3. 通过实验对比十大排序算法
  4. 电商ERP进销存系统V3多仓库带扫描php网页版B/S进销存系统多仓库版
  5. 推荐常用的排序学习算法——BPR(贝叶斯个性化排序)
  6. 本周AI热点回顾:无人车的眼睛、UP主的生产力工具,都藏在百度CVPR的这十个冠军里;IJCAI 2021心电全景图及其生成网络...
  7. iview自定义表单验证
  8. Python -- 网络编程
  9. 2015年新的开始新的生活
  10. 鸿蒙是另一种安卓吗,鸿蒙不是另一个安卓或者iOS!鸿蒙2.0上线倒计时