springboot 动态添加定时任务(配合数据库增删查改定时任务)

Quartz作业存储方式

Quartz提供两种基本作业存储类型。第一种类型叫做RAMJobStore,第二种类型叫做JDBC作业存储。

RAMJobStore和JDBC作业存储比较

RAMJobStore和JDBC作业存储比较
类型优点缺点
RAMJobStore不要外部数据库,配置容易,运行速度快因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到JVM内存里面,所以可以存储多少个Job和Trigger将会受到限制
JDBC作业存储支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务运行速度的快慢取决与连接数据库的快慢

通过综上比较得出:在一般实际的项目中,一般都采用jdbc的作业存储,但是jdbc作业存储的表结构复杂且表居多,大约十一张表,每张表结构也比较复杂。下面是官方要求的十一张表,具体如下:

jdbc作业存储的数据库表

但是我们其实主要一张表就行,记录定时任务的任务就行,其他虽然有作用,但不是很明显。

CREATE TABLE `sys_corn_quartz` (
  `uuid` varchar(255) COLLATE utf8_bin NOT NULL,
  `type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '类型',
  `job_group` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '任务组',
  `job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '任务名称',
  `invoke_param` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '参数',
  `quartz_time` datetime NOT NULL COMMENT '定时时间',
  `status` tinyint(4) NOT NULL DEFAULT '3' COMMENT '状态',
  `execute_endtime` datetime DEFAULT NULL COMMENT '执行任务时间',
  PRIMARY KEY (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

个人觉得定时任务这些字段基本都够用了。

我们先理清下思路,怎么实现一个实现流程?

首先我们要了解quartz三要素,了解了才知道步骤是怎么做?

  1. Scheduler:调度器。所有的调度都是由它控制。
  2. Trigger: 触发器。决定什么时候来执行任务。
  3. JobDetail & Job: JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。使用JobDetail + Job而不是Job,这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。

按照上面的三要素开始着手写代码实现。

代码实现:

第一步:pom 文件导入相对应的jar包

  
         <dependency>
             <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.3.1</version>
        </dependency>

第二步:在spring容器中注入scheduler统一调度器,当然也要配置一些scheduler的配置文件(ps:由于没有使用它的数据库配置,遂屏蔽了它的一些数据库配置文件,想用的朋友可以打开,但是要把这十一张表创建好)。

quartz.properties配置文件:

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

#线程池配置
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

#持久化配置
#org.quartz.jobStore.misfireThreshold = 50000
#org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#支持集群
#org.quartz.jobStore.isClustered = true
#org.quartz.jobStore.useProperties:true
#org.quartz.jobStore.clusterCheckinInterval = 15000

#使用weblogic连接Oracle驱动
#org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate
#org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#org.quartz.jobStore.tablePrefix = QRTZ_
#org.quartz.jobStore.dataSource = qzDS
#数据源连接信息,quartz默认使用c3p0数据源可以被自定义数据源覆盖
#org.quartz.dataSource.qzDS.driver = com.mysql.cj.jdbc.Driver
#org.quartz.dataSource.qzDS.URL = jdbc:mysql://127.0.0.1:3306/makes?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
#org.quartz.dataSource.qzDS.user = root
#org.quartz.dataSource.qzDS.password = 123456
#org.quartz.dataSource.qzDS.maxConnections = 10

加载配置文件并注入scheduler调度器实例

@Configuration
public class SchedulerConfig  {

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        //获取配置属性
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        //在quartz.properties中的属性被读取并注入后再初始化对象
        propertiesFactoryBean.afterPropertiesSet();
        //创建SchedulerFactoryBean
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setQuartzProperties(propertiesFactoryBean.getObject());
        //这样当spring关闭时,会等待所有已经启动的quartz job结束后spring才能完全shutdown。
        factory.setWaitForJobsToCompleteOnShutdown(true);
        factory.setOverwriteExistingJobs(false);
        factory.setStartupDelay(1);
        return factory;
    }


    /**
     * 通过SchedulerFactoryBean获取Scheduler的实例
     */
    @Bean(name="scheduler")
    public Scheduler scheduler() throws IOException {
        return schedulerFactoryBean().getScheduler();
    }

    /*@Bean 不要加,否则会重复加载
    public QuartzInitializerListener executorListener() {
        return new QuartzInitializerListener();
    }*/
}

第三步:创建我们的触发器Trigger 和JobDetail (即什么时候触发,触发条件是什么),我这边是把这个做成一个服务,方便其他地方动态调用,我这里只写了创建或者修改删除定时任务,比如暂停,直接调用Scheduler 里面的方法即可,都比较简单,我就不一一列举出来了

@Service
public class JobService {

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private CornTriggerQuartzMapper cornTriggerQuartzMapper;


    /**
     * 新建一个任务
     *
     */
    public String updateJob(CornTriggerQuartz cornTriggerQuartz) throws SchedulerException  {

        if(cornTriggerQuartz.getQuartzTime().before(new Date())){
            return "cron time must be after now";
        }
        cornTriggerQuartz.setQuartzTime(DateUtil.afterTime(new Date(), Calendar.MINUTE,2));
        String quartzStr = DateUtil.transfToQuartzStr(cornTriggerQuartz.getQuartzTime());
        //校验定时器数据格式,表达式格式不正确
        if (!CronExpression.isValidExpression(quartzStr)) {
            return "Illegal cron expression";
        }

        JobDetail jobDetail=null;
        //构建job信息
        if(WorkOrderJob.class.getSimpleName().equals(cornTriggerQuartz.getJobGroup())) {
            jobDetail = JobBuilder.newJob(WorkOrderJob.class)
                    .withIdentity(cornTriggerQuartz.getJobName(), cornTriggerQuartz.getJobGroup()).build();
        }

        if(jobDetail == null){
            throw new SchedulerException("暂无构建的对象");
        }

        //表达式调度构建器(即任务执行的时间,不立即执行)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
                        .cronSchedule(quartzStr)
                        .withMisfireHandlingInstructionDoNothing();

        //按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(cornTriggerQuartz.getJobName(), cornTriggerQuartz.getJobGroup())
                    .startAt(cornTriggerQuartz.getQuartzTime())
                    .withSchedule(scheduleBuilder).build();

        //传递参数
        if(ObjectUtil.StringIsNotNull(cornTriggerQuartz.getInvokeParam())) {
            trigger.getJobDataMap().put(Commons.Quartz.workorder.getInvokeParamKey(),cornTriggerQuartz.getInvokeParam());
        }

        TriggerKey triggerKey = TriggerKey.triggerKey(cornTriggerQuartz.getJobName(),cornTriggerQuartz.getJobGroup());
        //如果存在则修改,不存在则插入
        if(scheduler.checkExists(jobDetail.getKey()) && scheduler.checkExists(triggerKey)){
            scheduler.rescheduleJob(triggerKey, trigger);
        }else{
            scheduler.scheduleJob(jobDetail, trigger);
        }

        if(ObjectUtil.StringIsNotNull(cornTriggerQuartz.getUuid())){
            cornTriggerQuartzMapper.updateById(cornTriggerQuartz);
        }else{
            cornTriggerQuartzMapper.insert(cornTriggerQuartz);
        }

        return "success";
    }


    /**
     * 删除某个任务
     * @return
     * @throws SchedulerException
     */
    public String  deleteJob(String jobGroupName,String jobName) throws SchedulerException {
        JobKey jobKey = new JobKey(jobName, jobGroupName);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null ) {
            return "jobDetail is null";
        }else if(!scheduler.checkExists(jobKey)) {
            return "jobKey is not exists";
        }else {
            scheduler.deleteJob(jobKey);
            return "success";
        }
    }

第四步:定时器执行时执行的方法类Job,定义一个WorkOrderJob类实现 Job接口中execute方法。

@Component
public class WorkOrderJob implements Job {

    private static final Logger log = LoggerFactory.getLogger(WorkOrderJob.class);

    @Override
    public void execute(JobExecutionContext context)  {
        log.debug("★★★★★★★★★★★"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+ "定时任务开始★★★★★★★★★★★");
        //获取定时器传输的参数
        JobDataMap jobDataMap = context.getMergedJobDataMap();
        //.....执行业务逻辑
    }
}

第五步:考虑可能发生的情况

1.有多个触发器,可以创建多个Job,但是记得在JobDeatil 中根据类型执行不同的job实现,实现比较简单,你们自行实现

2.重启之后定时任务会销毁的情况(思路:在spring容器重启之后,自动把定时任务加入到队列中)

  • 创建ApplicationStartedListener 类,实现ApplicationListener<ContextRefreshedEvent>接口中的onApplicationEvent,可以从这方法中获取spring容器的bean。

/**
 * spring容器启动监听
 * @author fangh
 */
public class ApplicationStartedListener implements ApplicationListener<ContextRefreshedEvent> {

    private static final Logger logger = LoggerFactory.getLogger(ApplicationStartedListener.class);

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {

        if(event!= null && event.getApplicationContext()!= null){

            //获取定时任务mapper对象,然后查询数据库
            CornTriggerQuartzMapper cornTriggerQuartzMapper = ApplicationContextUtil.getBean(CornTriggerQuartzMapper.class);
            JobService jobService = ApplicationContextUtil.getBean(JobService.class);
            QueryWrapper<CornTriggerQuartz> cornTriggerQuartzQueryWrapper  =new QueryWrapper<>();
            cornTriggerQuartzQueryWrapper.eq("status" , Commons.Status.pend.getCode());
            List<CornTriggerQuartz> cornTriggerQuartzList = cornTriggerQuartzMapper.selectList(cornTriggerQuartzQueryWrapper);
            cornTriggerQuartzList.stream().forEach(cornTriggerQuartz -> {
                try {
                    jobService.updateJob(cornTriggerQuartz);
                } catch (SchedulerException e) {
                    logger.error(e.getMessage());
                }
            });
        }

    }
}
  • 从容器中获取实例,封装成工具类
/**
 * spring 容器中获取注入的bean 实例
 * @author  fangh
 */
@Getter
@Setter
public class ApplicationContextUtil implements ApplicationContextAware {

    /** 用于保存ApplicationContext的引用,set方式注入*/
    private static ApplicationContext applicationContext = null ;

    @Override
    public void setApplicationContext(ApplicationContext applicationContexts) throws BeansException {
        applicationContext = applicationContexts;
    }

    /**
     * 获取spring容器中属性
     * @param cls t
     * @return
     */
    public static <T> T getBean(Class<T> cls){
        return applicationContext.getBean(cls);
    }
}

 

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
在Spring Boot中创建定时任务配合数据库动态执行,可以通过使用Spring的定时任务注解@EnableScheduling和@Scheduled来实现。 首先,在Spring Boot的启动类上添加@EnableScheduling注解,这样Spring Boot就会自动扫描并创建定时任务。 接下来,在需要执行定时任务的方法上添加@Scheduled注解,该注解里面可以设置定时任务的触发条件,例如cron表达式、固定时间间隔等。 定时任务可以通过使用@Scheduled(cron = "cron表达式")来配置,其中cron表达式可以在配置文件中进行动态配置。假设我们需要在数据库中保存定时任务的cron表达式,可以通过创建一个定时任务配置表,将cron表达式保存在该表中。 在定时任务的方法中,我们可以通过访问数据库获取cron表达式并进行动态执行。具体步骤如下: 1. 创建一个定时任务配置表,包含字段id、cron_expression等。 2. 创建一个定时任务管理类,用于读取数据库配置,并动态创建定时任务。在该类中,可以使用JdbcTemplate或者Spring Data JPA来查询数据库获取cron表达式。 3. 创建一个定时任务类,用于执行具体的定时任务逻辑。 4. 在定时任务类中,注入定时任务管理类,并在方法上添加@Scheduled(cron = "cron表达式")注解。这样就可以根据数据库中的cron表达式执行相应的定时任务了。 通过以上步骤,我们就可以在Spring Boot中创建定时任务配合数据库动态执行了。在定时任务的执行过程中,可以根据数据库中的cron表达式来自动调整定时任务的触发时间。如果需要修改定时任务的执行时间,只需要更新数据库中的cron表达式即可。这样就实现定时任务动态执行。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值