一、前言
最近,因为saas系统需要支持 租户可配置数据源的需求,所以将系统改造为多数据源,从而也对定时任务进行改造。
二、代码实现
1、整合quartz
在项目工程中引入对应的包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
</dependency>
2、关于quartz的配置
以下是quartz的配置,由于quartz 需要数据库库存储定时任务数据,所以在配置的时候,需要配置一个主数据源,用于储存任务信息。
spring:
quartz:
# 使用数据库存储
job-store-type: jdbc
# 相同 Scheduler 名字的节点,形成一个 Quartz 集群
# scheduler-name: scheduler
# 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
wait-for-jobs-to-complete-on-shutdown: true
#配置的作业是否应覆盖现有的作业定义
overwrite-existing-jobs: true
jdbc:
# 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
initialize-schema: never
properties:
org:
quartz:
# JobStore 相关配置
jobStore:
# 使用的数据源
dataSource: demo
# JobStore 实现类
class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# Quartz 表前缀
tablePrefix: QRTZ_
# 是集群模式
isClustered: true
clusterCheckinInterval: 1000
useProperties: false
# 线程池相关配置
threadPool:
# 线程池大小。默认为 10 。
threadCount: 25
# 线程优先级
threadPriority: 5
# 线程池类型
class: org.quartz.simpl.SimpleThreadPool
scheduler:
# 调度标识名 集群中每一个实例都必须使用相同的名称
# 相同 Scheduler 名字的节点,形成一个 Quartz 集群
instanceName: scheduler
# ID设置为自动获取 每一个必须不同
instanceId: AUTO
#指定调度程序的主线程是否应该是守护线程
makeSchedulerThreadDaemon: true
2、job的生成
在多数据源的情况需要根据不同的数据源生成多份job,然后在job执行时需要切换不同的数据源。
2.1
在此我们先定义一个注解,然后在程序启动的时候将带有该注解的任务全部初始化生成任务
@Target(ElementType.TYPE)
@Documented
public @interface QuartzJob {
String name() default "";
String cron() default "";
String desc() default "";
}
2.2 初始化所有任务
在定义完注解类以后,这时候就需要一个loader 将业务代码中包含改注解的类转化成quartz 任务
/**
* * 任务加载器
* * 将所有的任务 初始化
*/
@Slf4j
public class JobLoader {
private ApplicationContext context;
private DynamicDataSourceProvider dynamicDataSourceProvider;
private JobManagement jobManagement;
public void init() {
Map<String, Object> quartzJobMap = context.getBeansWithAnnotation(QuartzJob.class);
Map<String, DataSource> dataSourceMap = dynamicDataSourceProvider.loadDataSources();
if (!ObjectUtils.isEmpty(quartzJobMap) || ObjectUtils.isEmpty(dataSourceMap)) {
dataSourceMap.keySet().forEach(sourceName -> {
log.info("========== init job start tenantId is {} ==============", sourceName);
quartzJobMap.values().forEach(job -> {
QuartzJob quartzJob = job.getClass().getAnnotation(QuartzJob.class);
if (ObjectUtils.isEmpty(quartzJob)){
log.error("loader job is fial !! jobClass is {}", job.getClass().getName());
}
try {
jobManagement.addJob(JobInfo.builder().jobName(sourceName + "-" + quartzJob.name())
.groupName(sourceName).desc(quartzJob.desc())
.beanClasz(quartzJob.jobClass()).cronStr(quartzJob.cron()).dataSourceName(sourceName).build());
} catch (SchedulerException e) {
log.error("add job is fial!! raeson:{}", e.getMessage());
}catch (Exception e){
log.error("add job is fial!! raeson:{}", e.getMessage());
}
});
log.info("========== init job is completed tenantId is {} ==============", sourceName);
});
}
}
}
2.3 添加监听器
在我们初始化任务以后,则是到了最重要一个环节,就是怎么在每个任务执行的时候去切换数据源。在此我使用了 quartz的 监听器,在每个任务执行前去做件事情。
/**
* * 任务监听器
* *
*/
@Slf4j
public class SchedulerListener implements JobListener {
@Override
public String getName() {
return JobDataKeyContant.DEFULT_SCHEDULER_NAME;
}
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
String dataSourceName = jobExecutionContext.getJobDetail().getJobDataMap().getString(JobDataKeyContant.DATA_SOURCE);
DynamicDataSourceContextHolder.push(dataSourceName);
log.info("quartz job is excutting ! jobName: {},dataSource: {}", jobExecutionContext.getJobDetail().getKey().toString(), dataSourceName);
}
}
}
至此 整个功能实现就完成了。