7、定时进行数据批处理任务

一、StopWatch时间控制类:

StopWatch 是spring工具包org.springframework.util下的一个工具类,主要用于计算同步单线程执行时间。

1、StopWatch优缺点:

优点:

(1)、spring自带工具类,可直接使用;

(2)、代码实现简单,使用更简单;

(3)、统一归纳,展示每项任务耗时与占用总时间的百分比,展示结果直观;

(4)、性能消耗相对较小,并且最大程度的保证了start与stop之间的时间记录的准确性;

(5)、可在start时直接指定任务名字,从而更加直观的显示记录结果;

缺点:

(1)、一个StopWatch实例一次只能开启一个task,不能同时start多个task,并且在该task未stop之前不能start一个新的task,必须在该task stop之后才能开启新的task,若要一次开启多个,需要new不同的StopWatch实例;

(2)、代码侵入式使用,需要改动多处代码;

2、StopWatch模板:

    public Object stopWatchDemo(){
        StopWatch stopWatch = new StopWatch("StopWatch测量业务逻辑的运行时间");
        stopWatch.start();
        log.info("====================StopWatch计时任务开始========================");

        //业务逻辑1
        //业务逻辑2
        //......

        stopWatch.stop();
        log.info("====================StopWatch计时任务结束========================");
        log.info(stopWatch.prettyPrint());  //打印执行时间
        return null;
    }

 

二、数据的批处理:

Java中的批处理用于执行一组插入操作或批处理,因为一次又一次地执行单个插入会浪费时间并降低性能。因此,使用批处理可以一次执行多个插入操作,这样可以提高程序的性能。

1、批处理方式一:

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    /**
     * 批处理插入
     */
    private void upSave1(List<BatchSaveVO> itemList) {
        SqlSession session = null;
        try {
            // 新获取一个模式为BATCH,自动提交为false的session
            // 如果自动提交设置为true,将无法控制提交的条数,改为最后统一提交,可能导致内存溢出
            session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
            BatchSaveMapper mapper = session.getMapper(BatchSaveMapper.class);
            int batchCount = 1000;// 每批commit的个数
            int batchLastIndex = batchCount;// 每批最后一个的下标
            for (int index = 0; index < itemList.size(); ) {
                if (batchLastIndex >= itemList.size()) {
                    batchLastIndex = itemList.size();
                    log.info("index:{}", batchLastIndex);
                    mapper.batchInsert(itemList.subList(index, batchLastIndex));
                    session.commit();
                    log.info("index:" + index + " batchLastIndex:" + batchLastIndex);
                    log.info("数据插入完毕");
                    break;// 数据插入完毕,退出循环
                } else {
                    mapper.batchInsert(itemList.subList(index, batchLastIndex));
                    session.commit();
                    log.info("index:" + index + " batchLastIndex:" + batchLastIndex);
                    index = batchLastIndex;// 设置下一批下标
                    batchLastIndex = index + (batchCount - 1);
                }
            }
            session.commit();
        } finally {
            session.close();
        }
    }

2、批处理方式二:

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    private void upSave2(List<BatchSaveVO> newReports) {
        SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
        // 如果自动提交设置为true,将无法控制提交的条数,改为最后统一提交,可能导致内存溢出
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, true);
        try {
            Optional.ofNullable(newReports).ifPresent(reports -> {
                BatchSaveMapper batchMapper = sqlSession.getMapper(BatchSaveMapper.class);
                batchMapper.batchInsert(newReports);
                sqlSession.commit();
            });
        }catch (Exception e){
            sqlSession.rollback();
            log.error("================保存失败================:{}",e.getMessage());
        }finally {
            sqlSession.close();
        }
    }

3、SqlSessionFactory详解:

(1)、介绍:

SqlSessionFactory是MyBatis的关键字,它是单个数据库映射关系经过编译后的内存镜像,SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象来获得,而SqlSessionFactoryBuildr则可以从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心。SqlSessionFactory是线程安全的,他一旦被创建,应该在应用执行期间都存在,在应用运行期间不要重复创建多次,建议使用单例模式,SqlSessionFactory是创建SqlSession的工厂。

(2)、SqlSessionFactory.openSession()方法:

//新获取一个模式为BATCH,自动提交为false的session
// 如果自动提交设置为true,将无法控制提交的条数,改为最后统一提交,可能导致内存溢出
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);

(3)、ExecutorType三种SQL执行器模式:

1)、ExecutorType.SIMPLE:相当于JDBC的stmt.execute(sql) 执行完毕即关闭即stmt.close()

2)、ExecutorType.REUSE:相当于JDBC的stmt.execute(sql) 执行完不关闭,而是将stmt存入Map<String, Statement>中缓存,其中key为执行的sql模板;

3)、ExecutorType.BATCH:相当于JDBC语句的stmt.addBatch(sql),即仅将执行SQL加入到批量计划但是不真正执行,所以此时不会执行返回受影响的行数,而只有执行stmt.execteBatch()后才会真正执行sql。

方式

优势

劣势

ExecutorType.SIMPLE

默认执行器, 节约服务器资源

每次都要开关Statement

ExecutorType.REUSE

提升后端接口处理效率

每次一个新sql都缓存,增大JVM内存压力

ExecutorType.BATCH

专门用于更新插入操作,效率最快

对select 不适用,另外特殊要求,比如限制一次execteBatch的数量时需要写过滤器定制

 

 

三、定时任务:

1、@Schedule定时任务方式:

在日常开发中比较简单的实现方式就是使用Spring的@Scheduled()注解。但是在修改服务器时间时会导致定时任务不执行情况的发生,解决的办法是当修改服务器时间后,将服务进行重启就可以避免此现象的发生。

(1)、@Schedule详解:

@Schedule详解参考

(2)、@Schedule的使用:

要使用@Scheduled注解,首先需要在相关类上添加@EnableScheduling,启用Spring的计划任务执行功能,这样可以在容器中的任何Spring管理的bean上检测@Scheduled注解,执行计划任务。

    @Scheduled(cron = "0 0 1 * * ?")
    public Object batchSaveWay() {
        StopWatch stopWatch = new StopWatch("@Scheduled定时任务");
        stopWatch.start();
        log.info("====================@Scheduled定时任务开始========================");

        //业务逻辑

        stopWatch.stop();
        log.info("====================@Scheduled定时任务结束========================");
        log.info(stopWatch.prettyPrint());
        return null;
    }

 

2、SchedulingConfigurer接口线程定时任务方式:

@Schedule 注解有一个缺点,其定时的时间不能动态的改变,而基于SchedulingConfigurer 接口的方式可以做到(例如将cron表达式存入数据库,通过修改数据中的cron表达式,动态改变定时任务的执行周期,而不需要重启项目。)

(1)、操作流程:

1)、声明ThreadPoolTaskScheduler配置类:

@Configuration
public class TaskPoolConfig {
    /**
     * ThreadPoolTaskExecutor是spring core包中的,而ThreadPoolExecutor是JDK中的JUC。
     * ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理。
     *
     * 拒绝策略:
     * (1)、CallerRunsPolicy: 当触发拒绝策略,只要线程池没有关闭的话,则使用调用 线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。
     *     但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
     * (2)、AbortPolicy: 丢弃任务,并抛出拒绝执行
     * (3)、RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
     * (4)、DiscardPolicy: 直接丢弃,其他啥都没有
     * (5)、DiscardOldestPolicy: 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
     */

    @Bean
    public ThreadPoolTaskScheduler taskSchedulerExecutor() {
        // 创建一个线程池对象
        final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        // 定义一个线程池大小
        scheduler.setPoolSize(100);
        // 线程池名的前缀
        scheduler.setThreadNamePrefix("taskExecutor-");
        // 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        // 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
        scheduler.setAwaitTerminationSeconds(60);
        // 线程池对拒绝任务的处理策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
        scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return scheduler;
    }
}

2)、实现SchedulingConfigurer接口:

@EnableScheduling
@Component
@Configuration
public abstract class ThreadSchedulingConfigurerWay implements SchedulingConfigurer {
    @Autowired
    private TaskPoolConfig taskPoolConfig;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(taskPoolConfig.taskSchedulerExecutor());
        scheduledTaskRegistrar.addTriggerTask(new Runnable() {
            @Override
            public void run() {
                // 定时任务要执行的内容
                executionWay();
            }
        }, new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                // 定时任务触发,可修改定时任务的执行周期
                String cron = getCron(); //可以将表达式配置在数据库中,动态改变定时的时间
                CronTrigger trigger = new CronTrigger(cron);
                Date nextExecDate = trigger.nextExecutionTime(triggerContext);
                return nextExecDate;
            }
        });
    }

    public abstract String getCron();
    public abstract void executionWay();
    
}

3)、实现定时线程任务:

任务一:

//定时线程任务一
@Configuration
public class ThreadSchedulingConfigurerWayOneImpl extends ThreadSchedulingConfigurerWay {
    @Override
    public String getCron() {
        String cron = "0 0/1 * * * ?";
        return cron;
    }

    @Override
    public void executionWay() {
        System.out.println("定时线程任务demo1:"
                + LocalDateTime.now()+",线程名称:"+Thread.currentThread().getName()
                + " 线程id:"+Thread.currentThread().getId());
    }
}

任务二:

//定时线程任务二
@Configuration
public class ThreadSchedulingConfigurerWayTwoImpl extends ThreadSchedulingConfigurerWay {
    @Override
    public String getCron() {
        String cron = "0 0/1 * * * ?";
        return cron;
    }

    @Override
    public void executionWay() {
        System.out.println("定时线程任务demo2:"
                + LocalDateTime.now()+",线程名称:"+Thread.currentThread().getName()
                + " 线程id:"+Thread.currentThread().getId());
    }
}

(2)、ThreadPoolTaskExecutor和ThreadPoolTaskScheduler的区别:

相关参考

 

3、Quartz框架定时任务方式:

Quartz框架定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行,如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。

(1)、添加Quartz依赖:

<!-- quartz定时框架-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

(2)、声明Job任务(继承QuartzJobBean抽象类):

@Slf4j
public class QuartzBatchSaveJob extends QuartzJobBean {
    @Autowired
    @Qualifier("QuartzWay")
    private BatchSaveService quartzWay;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //定时任务的业务逻辑
        quartzWay.batchSaveWay();
    }
}

(3)、编写业务类:

    public Object batchSaveWay() {
        StopWatch stopWatch = new StopWatch("Quartz定时框架启动任务");
        stopWatch.start();
        log.info("====================Quartz定时框架启动任务开始========================");

        //业务逻辑

        stopWatch.stop();
        log.info("====================Quartz定时框架启动任务结束========================");
        log.info(stopWatch.prettyPrint());
        return null;
    }

(4)、将Job任务注册到Spring容器中:

@Configuration
public class QuartzConfig {
    //Quartz定时框架启动任务
    @Bean
    public JobDetail businessJobDetail(){
        return JobBuilder.newJob(QuartzBatchSaveJob.class)
                .withDescription("Quartz定时框架启动任务")
                .withIdentity(QuartzBatchSaveJob.class.getSimpleName())
                .storeDurably().build();
    }
    @Bean
    public Trigger businessJobTrigger() {
        String cron = "0 0 1 * * ?";
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        return TriggerBuilder.newTrigger().forJob(businessJobDetail())
                .withIdentity("QuartzBatchSaveJobTrigger")
                .withSchedule(cronScheduleBuilder).build();
    }
}

 

 

 

 

 

 

 

 

posted on 2022-08-15 17:12  爱文(Iven)  阅读(842)  评论(0编辑  收藏  举报

导航