如果一张车票,被A乘客选中,且停留在付款页面,那么这张车票是不能出现在其他购票用户是不能购买的,因为该车票已经暂时被锁定,那么假如A乘客迟迟停留在付款页面,20秒钟过后,还未支付该订单,那么系统的定时器的功能会执行调度器去查询支付状态,如果未支付,我们的定时器工作要实现此功能,将车票解除锁定,重新改为可用的状态,那么A用户和其他的用户又能够购买该票。这时候就需要quartz自动调度器来实现。
本项目实例是基于idea开发软件进行编码,在spring boot中结合quartz使用,如果订单在20秒钟查询编号为1的车票,如果十秒之后还在付款,则取消该车票订单,将车票的锁定状态重新改为可用。
步骤:
新建Spring Boot项目
1.在Mysql的quartz数据库建立4表和在idea建相关包
2.使用项目内部的mybatis插件,将下列的sql导入到Mysql运行,然后借助插件自动生成实体类和mapper 使用教程
-- 需要定时器执行操作的实体类,这里是以订单表为例
create table t_order
(
order_id int primary key auto_increment, -- 订单ID
order_no varchar(50) not null, -- 订单编号
customer_id int not null, -- 客户ID
order_status int not null, -- 状态:0 待付款 1 已付款 2 已取消(客户) 3 已取消(卖家) 4 已取消(付款超时)
create_timestamp timestamp NULL default CURRENT_TIMESTAMP -- 创建日期时间:默认为系统当前时间
);
-- 注意:job_name存放的任务类的全路径,在quartz中通过jobName和jobGroup来确定trigger的唯一性,所以这两列为联合唯一索引
create table t_schedule_trigger
(
id int primary key auto_increment, -- ID
cron varchar(200) not null, -- 时间表达式
status char(1) not null, -- 使用状态 0:禁用 1:启用
job_name varchar(200) not null, -- 任务名称
job_group varchar(200) not null, -- 任务分组
unique index(job_name,job_group)
);
-- 额外添加到任务中的参数
create table t_schedule_trigger_param
(
param_id int primary key auto_increment, -- ID
name varchar(200) not null, -- 参数名
value varchar(512), -- 参数值
schedule_trigger_id int not null, -- 外键:引用t_schedule_trigger(id)
foreign key(schedule_trigger_id) references t_schedule_trigger(id)
);
注意生成之后的xml文件中配置的包的全限定名是否和项目一致。
3.在quartz包新建PayTicketJob.java接口,在订单支付状态是正在支付的状态,如果此状态持续20秒钟,那么这个工作类会去修改此票的状态。
此类需要调用:
OrderMapper.java接口的查询订单的selectPay和updateByPrimaryKey方法
ScheduleTriggerMapper.java接口的updateByPrimaryKey方法
selectPay:根据车票的id去查询到该订单的状态
updateByPrimaryKey:修改订单的状态
updateByPrimaryKey:修改触发器的状态
这里使用的调度器是表达式触发器
触发器的表达式生成网址:在线生成cron触发器的表达式网址
PayTicketJob.java
package com.springboot.ssm.quartz;
import com.springboot.ssm.mapper.OrderMapper;
import com.springboot.ssm.mapper.ScheduleTriggerMapper;
import com.springboot.ssm.model.Order;
import com.springboot.ssm.model.ScheduleTrigger;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author 小思
* @PackageName:com.springboot.ssm.quartz
* @ClassName: PayTicketJob
* @Description:十秒钟之后如果还在支付状态,则取消改订单的调度
* @date 2018/11/25 9:43
*/
//启用Lombok的Slf4j输出日志
@Slf4j
@Component
public class PayTicketJob implements Job {
static int count = 0;
@Autowired
private OrderMapper orderMapper;
@Autowired
private ScheduleTriggerMapper scheduleTriggerMapper;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("支付检查任务正在执行。。。" + new Date().toLocaleString());
//在付款页面的时候触发这个触发器
//查询3号车票票的支付状态(假如3号票被1号顾客购买了)
Order o = orderMapper.selectPay(3);
//支付状态有三种 0:未支付 1:正在支付 2:支付成功
//二十秒钟之后如未支付,则将车票状态修改,重新入库
if (o.getOrderStatus() == 1 && count == 2) {
o.setOrderStatus(0);
int n = orderMapper.updateByPrimaryKey(o);
//数据库影响行数大于0,则修改车票状态成功
if (n > 0) {
//将检查任务停止
ScheduleTrigger scheduleTrigger=scheduleTriggerMapper.selectByPrimaryKey(1);
scheduleTrigger.setStatus("0");
try {
scheduleTriggerMapper.updateByPrimaryKey(scheduleTrigger);
}
catch (Exception e){
}
System.out.println("支付检查任务执行完成!!!" + new Date().toLocaleString());
}
}
count++;
System.out.println(count);
}
}
4.在t_schedule_trigger表中插入刚刚新建的PayTicketJob.java接口的数据
建好数据之后,在ScheduleTriggerMapper.java添加查询所有的触发器的方法
queryAll,建的顺序是xml、java文件夹下的mapper,然后调用的工作类,自动注入mapper类,实际上应该再在service注入一层该方法。xml中的大部分增删改查的方法,都已经使用mybatis自动生成,我们根据动态sql语句变通就行。
5.将quartz工作时的触发器的详细信息的表导入到数据库中运行,此sql文件在quartz的官方文档有下载。下载它的demo,表存储在dbTables文件夹下。
tables_mysql.sql
#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# PLEASE consider using mysql with innodb tables to avoid locking issues
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
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)
);
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)
);
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)
);
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(200) 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)
);
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)
);
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),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
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)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
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)
);
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)
);
CREATE TABLE QRTZ_LOCKS
(
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
commit;
运行结果:
注意这些表,quartz框架会自动管理数据,不需要手动设置。
6.新建QuartzConfigration.java和MyJobFactory.java
QuartzConfigration.java
package com.springboot.ssm.quartz;
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;
import java.io.IOException;
import java.util.Properties;
/**
* @author 小思
* @PackageName:com.springboot.ssm.quartz
* @ClassName: QuartzConfigration
* @Description:调度器的配置类
* @date 2018/11/25 9:53
*/
@Configuration
public class QuartzConfigration {
//自定义的工作工厂,负责产生Job
@Autowired
private MyJobFactory myJobFactory;
//获取工厂的调度器的工厂
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
try {
schedulerFactoryBean.setQuartzProperties(quartzProperties());
schedulerFactoryBean.setJobFactory(myJobFactory);
return schedulerFactoryBean;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//指定quartz.properties
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
//创建schedule调度器
@Bean(name = "scheduler")
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
MyJobFactory.java
package com.springboot.ssm.quartz;
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;
/**
* @author 小思
* @PackageName:com.springboot.ssm.quartz
* @ClassName: MyJobFactory
* @Description:将Job类授予spring管理
* @date 2018/11/25 10:53
*/
@Component
public class MyJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);//qz框架反射机制创建jobBean
capableBeanFactory.autowireBean(jobInstance); //这一步解决不能spring注入bean的问题
return jobInstance;
}
}
7.项目的启动文件配置
application.properties
#服务器配置
server.port=8090
server.servlet.context-path=/
#数据源配置
spring.datasource.username=root
spring.datasource.data-password=
spring.datasource.url=jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#mybatis的配置
mybatis.type-aliases-package=com.springboot.ssm.model
mybatis.mapper-locations=classpath*:mapper/*.xml
8.SsmApplication.java启动文件的设置
package com.springboot.ssm;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@MapperScan("com.springboot.ssm.mapper")
public class SsmApplication {
public static void main(String[] args) {
SpringApplication.run(SsmApplication.class, args);
}
}
9.运行SsmApplication.java文件,触发器启动,每十秒次检查一次,定义了一个变量保存运行次数,运行第二次结束后,也就是等待了20秒,客户还没有支付,所以将订单状态改为0,默认的数据库数据是1,正在支付的状态。然后将触发器的状态改为0,0是会运行的,1是不运行的,但是这里有一个bug,也就是在运行状态中修改了触发器的状态,触发器还是照原先的运行状态运行,需要重启才行。
另外补充:
在执行任务的过程中可以传递参数到表中去,在触发器中可以获取该参数信息
只需要在ScheduleTriggerServiceImpl.java(传递参数信息)中添加下面代码块
//TODO 可以添加一些额外的参数到任务的上下文中
//参数的传递
//获取当前执行的任务的id
Integer id=scheduleJob.getId();
//通过任务的触发器的Id获取参数表的参数信息
List<ScheduleTriggerParam> scheduleTriggerParams=scheduleTriggerParamMapper.findScheduleTriggerParamById(id);
//保存参数信息
JobDataMap jobDataMap=jobDetail.getJobDataMap();
//遍历参数表中的信息
for (ScheduleTriggerParam scheduleTriggerParam : scheduleTriggerParams) {
jobDataMap.put(scheduleTriggerParam.getName(),scheduleTriggerParam.getValue());
}
PayTicketJob.java(接收参数信息)
//获取触发器中的参数信息
JobDataMap jobDataMap=jobExecutionContext.getMergedJobDataMap();
String name=(String)jobDataMap.get("name");
String age=(String)jobDataMap.get("age");
System.out.println("name="+name + "______age=" + age);
这只是quartz在springboot+ssm的简单运用,在下的理解如上,欢迎大家补充和斧正!!!
完整项目已经上传,可以帮助理解项目的整体结构和理解quartz的运行机制,里面包含数据库数据和官方文档,下载地址
说在最后的话:编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~