最近写项目有个需求要求定时将另一个服务器上的数据库的数据导入到项目服务器的数据库。(有点hp)

这中间用到了两个之前不知道的技术

一、 定时任务

使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:

1、基于注解(@Scheduled)

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class SaticScheduleTask {
    //3.添加定时任务
    @Scheduled(cron = "0/5 * * * * ?")
    //或直接指定时间间隔,例如:5秒
    //@Scheduled(fixedRate=5000)
    private void configureTasks() {
        System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
    }
}

@Scheduled:除了支持灵活的参数表达式cron之外,还支持简单的延时操作,例如 fixedDelay ,fixedRate 填写相应的毫秒数即可。
有兴趣的可以了解下cron表达式 ,这里就不再赘述

这种方法的缺点是当我们调整了执行周期的时候,需要重启应用才能生效,这多少有些不方便。为了达到实时生效的效果,可以使用接口来完成定时任务。

2、基于接口(SchedulingConfigurer)

前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。

1. 我们首先要数据库插入数

在这里插入图片描述
2. java代码配置

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer {

    @Mapper
    public interface CronMapper {
        @Select("select cron from cron limit 1")
        public String getCron();
    }

    @Autowired      //注入mapper
    @SuppressWarnings("all")
    CronMapper cronMapper;

    /**
     * 执行定时任务.
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

        taskRegistrar.addTriggerTask(
                //1.添加任务内容(Runnable)
                () -> System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime()),
                //2.设置执行周期(Trigger)
                triggerContext -> {
                    //2.1 从数据库获取执行周期
                    String cron = cronMapper.getCron();
                    //2.2 合法性校验.
                    if (StringUtils.isEmpty(cron)) {
                        // Omitted Code ..
                    }
                    //2.3 返回执行周期(Date)
                    return new CronTrigger(cron).nextExecutionTime(triggerContext);
                }
        );
    }

}

注意: 如果在数据库修改时格式出现错误,则定时任务会停止,即使重新修改正确;此时只能重新启动项目才能恢复。

3、基于注解设定多线程定时任务

//@Component注解用于对那些比较中立的类进行注释;
//相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@Component
@EnableScheduling   // 1.开启定时任务
@EnableAsync        // 2.开启多线程
public class MultithreadScheduleTask {

        @Async
        @Scheduled(fixedDelay = 1000)  //间隔1秒
        public void first() throws InterruptedException {
            System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
            System.out.println();
            Thread.sleep(1000 * 10);
        }

        @Async
        @Scheduled(fixedDelay = 2000)
        public void second() {
            System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
            System.out.println();
        }
    }

二. on duplicate key update导致主键不连续自增解决方法

关于这个业务 我的实现思路是先将另一个服务器的数据库数据先查出来(业务只涉及两张表)然后封装到list集合中。(我创建了改表的实体类) 然后再用mybatis的动态sql 遍历插入本项目的表中。下面是我的sql语句
在这里插入图片描述
在这里插入图片描述
关于这个 sql语句我解释一下,这个是mysql特有的语法,在insert 时根据主键或唯一键进行判断,如果已有则修改,如果没有则 新增

原本这个sql语句已经能够达到我的需求,但我没想到,在测试了几次后出现了自增键数值跳跃问题,直接从1000 跳到了 3000,后来经过查找资料发现是 on duplicate key update的问题

解决这个问题,有两种方式,第一种是修改innodb_autoinc_lock_mode中的模式,第二种是将语句修拆分为更新和操作2个动作

第一种方式: 修改innodb_autoinc_lock_mode配置

innodb_autoinc_lock_mode中有3种模式, 0 1 和 2,mysql5 的默认配置是1,

  • 0是每次分配自增id的时候都会锁表.这个对并发不太支持

  • 1只有在bulk insert的时候才会锁表,简单insert的时候只会使用一个light-weight mutex,比0的并发性能高

  • 2.没有仔细看,好像是很多的不保证…不太安全.

数据库默认是1的情况下, 就会发生上面的那种现象,每次使用 insert into … on duplicate key update 的时候都会把简单自增id增加,不管是发生了insert还是update

由于该代码数据量大,同时需要更新和添加的数据量多,不能使用将0模式,只能将数据库代码拆分成为更新和插入2个步骤。

具体代码我就不再演示。分开来的话只能是先修改后新增 新增时再判断它是否存在在库中。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐