技术栈:Springboot2.X + Quartz +FastMybatis (自认为比mybatis-plus更方便的框架) + Hutool + lombok
FastMybatis框架博客:
01、崛起于Springboot2.X之入门FastMybatis: my.oschina.net/mdxlcj/blog/1835665 02、崛起于Springboot2.X之整合FastMybatis精装版:my.oschina.net/mdxlcj/blog/3059687 03、FastMybatis框架快速入门:官方文档
Quartz入门级框架博客:
04、崛起于Springboot2.X之集成Quartz定时调度: my.oschina.net/mdxlcj/blog/1862472
序言:之前公司有一个需求说是可以通过一个界面来操作定时任务的时间、包括修改、启动、关闭等功能,而不是在代码中写死,那个时候从网上找了很久都没有找到,博客都特别初级,只能算是入门的后来就不了了之了,目前已经实现开发出这套功能,公开一下,我也希望能够帮助更多的人在企业更加快速的实现该功能,如图:
然后我们看一下,新增定时任务配置截图:
这个功能也有批量暂停定时任务的功能、立即恢复、以及执行,当然还有定时任务启动时的日志记录,如图:
所以接下来就进入开发阶段,由于前端页面是用vue写的,我就不把前端代码弄出来了,反而这样会让大家使用起来比较费力,所以我们就不用vue进行测试了,只用后段自己生成数据来测试就可以了,也能够达到目的的。
1、pom.xml
<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>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.6.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>net.oschina.durcframework</groupId> <artifactId>fastmybatis-spring-boot-starter</artifactId> <version>1.8.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
2、application.properties
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/ss?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=root
3、mysql表结构
CREATE TABLE `schedule_job` ( `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务id', `bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称', `method_name` varchar(100) DEFAULT NULL COMMENT '方法名', `params` varchar(2000) DEFAULT NULL COMMENT '参数', `cron_expression` varchar(100) DEFAULT NULL COMMENT 'cron表达式', `status` tinyint(4) DEFAULT NULL COMMENT '任务状态 0:启动 1:搁置', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`job_id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='定时任务' CREATE TABLE `schedule_job_log` ( `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志id', `job_id` bigint(20) NOT NULL COMMENT '任务id', `bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称', `method_name` varchar(100) DEFAULT NULL COMMENT '方法名', `params` varchar(2000) DEFAULT NULL COMMENT '参数', `status` tinyint(4) NOT NULL COMMENT '任务状态 0:成功 1:失败', `error` varchar(2000) DEFAULT NULL COMMENT '失败信息', `times` int(11) NOT NULL COMMENT '耗时(单位:毫秒)', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`log_id`), KEY `job_id` (`job_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='定时任务日志'
4、实体类
import lombok.Data; import javax.persistence.*; import java.io.Serializable; import java.util.Date; @Data @Table(name = "schedule_job") public class ScheduleJobEntity implements Serializable { private static final long serialVersionUID = 1L; public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY"; //任务调度参数key @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "job_id") private Long jobId; //任务id @Column(name = "bean_name") private String beanName; //spring bean名称 @Column(name = "method_name") private String methodName; //方法名 @Column(name = "params") private String params; //参数 @Column(name = "cron_expression") private String cronExpression; //cron表达式 @Column(name = "status") private Integer status; //任务状态 @Column(name = "remark") private String remark; //备注 @Column(name = "create_time") private Date createTime; //创建时间 }
import lombok.Data; import javax.persistence.*; import java.io.Serializable; import java.util.Date; /** * 定时执行日志 */ @Data @Table(name = "schedule_job_log") public class ScheduleJobLogEntity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "log_id") private Long logId; //日志id @Column(name = "job_id") private Long jobId; //任务id @Column(name = "bean_name") private String beanName; //spring bean名称 @Column(name = "method_name") private String methodName;//方法名 @Column(name = "params") private String params; //参数 @Column(name = "status") private Integer status; // 任务状态 0:成功 1:失败 @Column(name = "error") private String error; //失败信息 @Column(name = "times") private Integer times; //耗时(单位:毫秒) @Column(name = "create_time") private Date createTime; //创建时间 }
请求接口统一返回实体类
import lombok.AllArgsConstructor; import lombok.Data; import lombok.ToString; @Data @AllArgsConstructor @ToString public class ResultData { private boolean success; private String code; private String message; private Object data; public ResultData() { this.success = true; this.code = "200"; } }
5、Util层
这一层代码大家看不明白没有关系,毕竟这些都是工具类里面的,不是我们开发日常编码那种,所以只需要配置一次就好啦,正常开发我们就直接跳过第五步就好了。
import org.apache.commons.lang.StringUtils; import org.quartz.JobExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.quartz.QuartzJobBean; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ScheduleJob extends QuartzJobBean { private Logger logger = LoggerFactory.getLogger(getClass()); private ExecutorService service = Executors.newSingleThreadExecutor(); @Override protected void executeInternal(JobExecutionContext context) { ScheduleJobEntity scheduleJob = (ScheduleJobEntity) context.getMergedJobDataMap() .get(ScheduleJobEntity.JOB_PARAM_KEY); //获取spring bean ScheduleJobService scheduleJobService = (ScheduleJobService) SpringContextUtils.getBean("scheduleJobService"); //数据库保存执行记录 ScheduleJobLogEntity log = new ScheduleJobLogEntity(); log.setJobId(scheduleJob.getJobId()); log.setBeanName(scheduleJob.getBeanName()); log.setMethodName(scheduleJob.getMethodName()); log.setParams(scheduleJob.getParams()); log.setCreateTime(new Date()); //任务开始时间 long startTime = System.currentTimeMillis(); try { //执行任务 logger.info("任务准备执行,任务ID:" + scheduleJob.getJobId()); ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getBeanName(), scheduleJob.getMethodName(), scheduleJob.getParams()); Future<?> future = service.submit(task); future.get(); //任务执行总时长 long times = System.currentTimeMillis() - startTime; log.setTimes((int) times); //任务状态 0:成功 1:失败 log.setStatus(0); logger.info("任务执行完毕,任务ID:" + scheduleJob.getJobId() + " 总共耗时:" + times + "毫秒"); } catch (Exception e) { logger.error("任务执行失败,任务ID:" + scheduleJob.getJobId(), e); //任务执行总时长 long times = System.currentTimeMillis() - startTime; log.setTimes((int) times); //任务状态 0:成功 1:失败 log.setStatus(1); log.setError(StringUtils.substring(e.toString(), 0, 2000)); } finally { scheduleJobService.saveLog(log); } } }
import org.apache.commons.lang.StringUtils; import org.springframework.util.ReflectionUtils; import java.lang.reflect.Method; public class ScheduleRunnable implements Runnable { private Object target; private Method method; private String params; public ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException { this.target = SpringContextUtils.getBean(beanName); this.params = params; if (StringUtils.isNotBlank(params)) { this.method = target.getClass().getDeclaredMethod(methodName, String.class); } else { this.method = target.getClass().getDeclaredMethod(methodName); } } @Override public void run() { try { ReflectionUtils.makeAccessible(method); if (StringUtils.isNotBlank(params)) { method.invoke(target, params); } else { method.invoke(target); } } catch (Exception e) { throw new RuntimeException("执行定时任务失败", e); } } }
import org.quartz.*; public class ScheduleUtils { private final static String JOB_NAME = "TASK_"; /** * 获取触发器key */ public static TriggerKey getTriggerKey(Long jobId) { return TriggerKey.triggerKey(JOB_NAME + jobId); } /** * 获取jobKey */ public static JobKey getJobKey(Long jobId) { return JobKey.jobKey(JOB_NAME + jobId); } /** * 获取表达式触发器 */ public static CronTrigger getCronTrigger(Scheduler scheduler, Long jobId) { try { return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId)); } catch (SchedulerException e) { throw new RuntimeException("获取定时任务CronTrigger出现异常", e); } } /** * 创建定时任务 */ public static void createScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) { try { //构建job信息 JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getJobId())).build(); //表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()) .withMisfireHandlingInstructionDoNothing(); //按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getJobId())).withSchedule(scheduleBuilder).build(); //放入参数,运行时的方法可以获取 jobDetail.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob); scheduler.scheduleJob(jobDetail, trigger); //暂停任务 if (scheduleJob.getStatus() == 1) { pauseJob(scheduler, scheduleJob.getJobId()); } } catch (SchedulerException e) { throw new RuntimeException("创建定时任务失败", e); } } /** * 更新定时任务 */ public static void updateScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) { try { TriggerKey triggerKey = getTriggerKey(scheduleJob.getJobId()); //表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()) .withMisfireHandlingInstructionDoNothing(); CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getJobId()); //按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); //参数 trigger.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob); scheduler.rescheduleJob(triggerKey, trigger); //暂停任务 if (scheduleJob.getStatus() == 1) { pauseJob(scheduler, scheduleJob.getJobId()); } } catch (SchedulerException e) { throw new RuntimeException("更新定时任务失败", e); } } /** * 立即执行任务 */ public static void run(Scheduler scheduler, ScheduleJobEntity scheduleJob) { try { //参数 JobDataMap dataMap = new JobDataMap(); dataMap.put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob); scheduler.triggerJob(getJobKey(scheduleJob.getJobId()), dataMap); } catch (SchedulerException e) { throw new RuntimeException("立即执行定时任务失败", e); } } /** * 暂停任务 */ public static void pauseJob(Scheduler scheduler, Long jobId) { try { scheduler.pauseJob(getJobKey(jobId)); } catch (SchedulerException e) { throw new RuntimeException("暂停定时任务失败", e); } } /** * 恢复任务 */ public static void resumeJob(Scheduler scheduler, Long jobId) { try { scheduler.resumeJob(getJobKey(jobId)); } catch (SchedulerException e) { throw new RuntimeException("暂停定时任务失败", e); } } /** * 删除定时任务 */ public static void deleteScheduleJob(Scheduler scheduler, Long jobId) { try { scheduler.deleteJob(getJobKey(jobId)); } catch (SchedulerException e) { throw new RuntimeException("删除定时任务失败", e); } } }
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringContextUtils implements ApplicationContextAware { public static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextUtils.applicationContext = applicationContext; } public static Object getBean(String name) { return applicationContext.getBean(name); } public static <T> T getBean(String name, Class<T> requiredType) { return applicationContext.getBean(name, requiredType); } public static boolean containsBean(String name) { return applicationContext.containsBean(name); } public static boolean isSingleton(String name) { return applicationContext.isSingleton(name); } public static Class<? extends Object> getType(String name) { return applicationContext.getType(name); } /** * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. */ public static <T> T getBean(Class<T> requiredType) { return applicationContext.getBean(requiredType); } }
import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import java.util.Set; public class ValidatorUtils { private static Validator validator; static { validator = Validation.buildDefaultValidatorFactory().getValidator(); } /** * 校验对象 * * @param object 待校验对象 * @param groups 待校验的组 * @throws BusinessException 校验不通过,则报BusinessException异常 */ public static void validateEntity(Object object, Class<?>... groups) throws RuntimeException { Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups); if (!constraintViolations.isEmpty()) { ConstraintViolation<Object> constraint = (ConstraintViolation<Object>) constraintViolations.iterator().next(); throw new RuntimeException(constraint.getMessage()); } } }
6、dao层
import com.gitee.fastmybatis.core.mapper.CrudMapper; import java.util.List; import java.util.Map; public interface ScheduleJobDao extends CrudMapper<ScheduleJobEntity,Integer> { int updateBatchStatus(Map<String, Object> map); List<ScheduleJobEntity> queryList(Map<String, Object> map); ScheduleJobEntity queryObject(Long jobId); int queryTotal(Map<String, Object> map); void deleteBatch(Long[] jobIds); }
import com.gitee.fastmybatis.core.mapper.CrudMapper; public interface ScheduleJobLogDao extends CrudMapper<ScheduleJobLogEntity,Long> { ScheduleJobLogEntity queryObject(Long jobId); }
7、Service层
接口
public interface ScheduleJobService { /** * 根据ID,查询定时任务 */ ScheduleJobEntity queryObject(Long jobId); /** * 查询定时任务列表 */ List<ScheduleJobEntity> queryList(Map<String, Object> map); /** * 查询总数 */ int queryTotal(Map<String, Object> map); /** * 保存定时任务 */ void save(ScheduleJobEntity scheduleJob); /** * 更新定时任务 */ void update(ScheduleJobEntity scheduleJob); /** * 批量删除定时任务 */ void deleteBatch(Long[] jobIds); /** * 批量更新定时任务状态 */ int updateBatch(Long[] jobIds, int status); /** * 立即执行 */ void run(Long[] jobIds); /** * 暂停运行 */ void pause(Long[] jobIds); /** * 恢复运行 */ void resume(Long[] jobIds); /** * 定时任务列表+总数 */ ResultData listJob(int pageIndex, int pageSize); /** * 日志查询 */ ResultData queryLogObject(Long jobId); /** * 保存日志记录 */ void saveLog(ScheduleJobLogEntity log); /** * 查询日志记录列表 */ ResultData listLog(int pageIndex, int pageSize); }
实现类
import com.gitee.fastmybatis.core.query.Query; import org.quartz.CronTrigger; import org.quartz.Scheduler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.List; import java.util.Map; @Service("scheduleJobService") public class ScheduleJobServiceImpl implements ScheduleJobService { @Autowired private Scheduler scheduler; @Autowired private ScheduleJobDao schedulerJobDao; @Autowired ScheduleJobLogDao jobLogDao; /** * 项目启动时,初始化定时器 */ @PostConstruct public void init(){ List<ScheduleJobEntity> scheduleJobList = schedulerJobDao.queryList(new HashMap<>()); for(ScheduleJobEntity scheduleJob : scheduleJobList){ CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getJobId()); //如果不存在,则创建 if(cronTrigger == null) { ScheduleUtils.createScheduleJob(scheduler, scheduleJob); }else { ScheduleUtils.updateScheduleJob(scheduler, scheduleJob); } } } @Override public ScheduleJobEntity queryObject(Long jobId) { return schedulerJobDao.queryObject(jobId); } @Override public List<ScheduleJobEntity> queryList(Map<String, Object> map) { return schedulerJobDao.queryList(map); } @Override public int queryTotal(Map<String, Object> map) { return schedulerJobDao.queryTotal(map); } @Override @Transactional public void save(ScheduleJobEntity scheduleJob) { schedulerJobDao.save(scheduleJob); ScheduleUtils.createScheduleJob(scheduler, scheduleJob); } @Override @Transactional public void update(ScheduleJobEntity scheduleJob) { ScheduleUtils.updateScheduleJob(scheduler, scheduleJob); schedulerJobDao.update(scheduleJob); } @Override @Transactional public void deleteBatch(Long[] jobIds) { for(Long jobId : jobIds){ ScheduleUtils.deleteScheduleJob(scheduler, jobId); } //删除数据 schedulerJobDao.deleteBatch(jobIds); } @Override public int updateBatch(Long[] jobIds, int status){ Map<String, Object> map = new HashMap<>(); map.put("list", jobIds); map.put("status", status); return schedulerJobDao.updateBatchStatus(map); } @Override @Transactional public void run(Long[] jobIds) { for(Long jobId : jobIds){ ScheduleUtils.run(scheduler, queryObject(jobId)); } } @Override @Transactional public void pause(Long[] jobIds) { for(Long jobId : jobIds){ ScheduleUtils.pauseJob(scheduler, jobId); } updateBatch(jobIds, 1); } @Override @Transactional public void resume(Long[] jobIds) { for(Long jobId : jobIds){ ScheduleUtils.resumeJob(scheduler, jobId); } updateBatch(jobIds, 0); } @Override public ResultData listJob(int pageIndex, int pageSize) { ResultData resultData = new ResultData(); //查询列表数据 Query query = new Query(); query.page(1,10); List<ScheduleJobEntity> jobList = schedulerJobDao.list(query); long total = schedulerJobDao.getCount(query); Map<String,Object> map = new HashMap<>(); map.put("total",total); map.put("list",jobList); resultData.setData(map); return resultData; } @Override public ResultData queryLogObject(Long jobId) { ResultData resultData = new ResultData(); resultData.setData(jobLogDao.getById(jobId)); return resultData; } @Override public void saveLog(ScheduleJobLogEntity log) { jobLogDao.save(log); } @Override public ResultData listLog(int pageIndex, int pageSize) { ResultData resultData = new ResultData(); //查询列表数据 Query query = new Query(); query.page(pageIndex,pageSize); List<ScheduleJobLogEntity> jobList = jobLogDao.list(query); long total = jobLogDao.getCount(query); Map<String,Object> map = new HashMap<>(); map.put("total",total); map.put("list",jobList); resultData.setData(map); return resultData; } }
8、Controller层
@RestController @RequestMapping("/schedule") public class ScheduleJobController { @Autowired private ScheduleJobService scheduleJobService; /** * 定时任务列表 */ @RequestMapping("/list") public ResultData list() { return scheduleJobService.listJob(1,10); } /** * 定时任务信息 */ @RequestMapping("/info/{jobId}") public ResultData info(@PathVariable("jobId") Long jobId) { ScheduleJobEntity schedule = scheduleJobService.queryObject(jobId); ResultData resultData = new ResultData(); resultData.setData(schedule); return resultData; } /** * 保存定时任务 @RequestBody ScheduleJobEntity scheduleJob */ @RequestMapping("/save") public ResultData save() { //先测试编写一个临时ScheduleJobEntity,毕竟后端页面不会写 ScheduleJobEntity jobEntity = createJob(); ValidatorUtils.validateEntity(jobEntity); scheduleJobService.save(jobEntity); return new ResultData(); } /** * 修改定时任务 */ @RequestMapping("/update") public ResultData update(@RequestBody ScheduleJobEntity scheduleJob) { ValidatorUtils.validateEntity(scheduleJob); scheduleJobService.update(scheduleJob); return new ResultData(); } /** * 删除定时任务 */ @RequestMapping("/delete") public ResultData delete(@RequestBody Long[] jobIds) { scheduleJobService.deleteBatch(jobIds); return new ResultData(); } /** * 立即执行任务 */ @RequestMapping("/run") public ResultData run(@RequestBody Long[] jobIds) { scheduleJobService.run(jobIds); return new ResultData(); } /** * 暂停定时任务 */ @RequestMapping("/pause") public ResultData pause(@RequestBody Long[] jobIds) { scheduleJobService.pause(jobIds); return new ResultData(); } /** * 恢复定时任务 */ @RequestMapping("/resume") public ResultData resume(@RequestBody Long[] jobIds) { scheduleJobService.resume(jobIds); return new ResultData(); } /** * 定时任务日志列表 */ @RequestMapping("/log/list") public ResultData listLog() { return scheduleJobService.listLog(1,10); } /** * 定时任务日志信息查询 */ @RequestMapping("/log/info/{logId}") public ResultData infoLog(@PathVariable("logId") Long logId) { return scheduleJobService.queryLogObject(logId); } public static ScheduleJobEntity createJob(){ ScheduleJobEntity jobEntity = new ScheduleJobEntity(); jobEntity.setBeanName("mytask"); jobEntity.setCreateTime(new Date()); jobEntity.setMethodName("firstTask"); jobEntity.setCronExpression("0/10 * * * * ?"); jobEntity.setParams(null); jobEntity.setStatus(1); jobEntity.setRemark("测试该定时任务的执行"); return jobEntity; } }
9、定时任务
该类涉及到@Component(value = "mytask")和方法名都将被前端页面使用,使用这两个属性值定位到何时执行该定时任务。
import org.springframework.stereotype.Component; /** * @Author:MuJiuTian * @Description: 页面控制定时任务的触发时间、开启和关闭都要有定时任务 * @Date: Created in 下午2:06 2019/8/27 */ @Component(value = "mytask") public class MyTask { public void firstTask(){ //再这个方法中执行task业务逻辑 System.out.println("该定时任务由前台页面控制何时启动"); } }
10、启动测试
谨记,启动类,千万不要添加@MapperScan(),因为集成了FastMybatis框架,已经自动封装了。如果大家不喜欢这个框架的话,也可以使用Mybatis或者流行的Mybatis-plus或者TKMybatis、EasyMybatis等等框架。
测试接口:http://localhost:8081/schedule/save,我的端口是8081的。
结果如图已经触发我们刚刚写的定时任务了:
成功啦!不喜欢用FastMybatis的话用其他框架操作mysql吧,毕竟我不太喜欢用mybatis-plus,毕竟需要多写代码。
来源:oschina
链接:https://my.oschina.net/u/3209213/blog/3098443