最近做了一个导出功能,需要将页面筛选条件之后的数据全部导出到excel中,每个分公司只能导出自己账号权限下的数据不会很多,但是如果是总公司不进行任何筛选直接导出全部数据大概会有17万左右的数据,后期可能还会更多,但是前端超时时间是30s。由于easyexcel不支持并发写入,所以只能从数据库方面入手。

自定义线程池

由于需要使用到多线程,最好自定义一个导出专用的线程池不要影响到其他功能


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class ThreadPoolConfiguration {
    
    /**
     * 导出任务线程池
     * @return
     */
    @Bean("exportTaskExecutor")
    public ThreadPoolTaskExecutor exportTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(5);
        // 最大线程数
        executor.setMaxPoolSize(10);
        // 等待队列数
        executor.setQueueCapacity(0);
        // 非核心线程空闲存活时间
        executor.setKeepAliveSeconds(60);
        // 线程名称
        executor.setThreadNamePrefix("export-task-");
        
        // 拒绝策略:由主线程继续执行被拒绝的任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(30);
        
        // 线程池初始化
        executor.initialize();
        return executor;
    }
}

多线程分页查询数据库并导出

// 设置文本内省
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        // 设置字符编码
        response.setCharacterEncoding("utf-8");
        String fileTimeStr = DateUtil.format(DateUtil.date(), "yyyyMMddHHmm");
        String fileName = URLEncoder.encode("清单导出_" + fileTimeStr, StandardCharsets.UTF_8);
        // 设置响应头
        response.setHeader("Content-disposition", "filename=" + fileName + ".xlsx");
        
        // 使用try-with-resources确保资源释放
        log.info("开始添加多线程导出");
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ExportItemListVo.class)
                .registerWriteHandler(new HorizontalCellStyleStrategy(CustomExcelStyleUtil.getHeadStyle(),CustomExcelStyleUtil.getContentStyle()))
                .build()
        ) {
            WriteSheet writeSheet = EasyExcel.writerSheet("标的清单").build();
            String finalCompanyCode = companyCode;
            // 提交当前批次任务
            List<Future<List<ExportItemListVo>>> batchFutures = new LinkedList<>();
            
            // 分页查询并写入
            for (int pageNum = 1; pageNum <= totalPages; pageNum++) {
                // 分页
                Page<ExportItemListVo> page = new Page<>(pageNum,
                        pageSize);
                page.setSearchCount(false);
                if (totalPages == 1) {
                    // 当只有一页则不分页  加速查询
                    batchFutures.add(exportTaskExecutor.submit(() -> itemMapService.exportItemList(params, finalCompanyCode, null)));
                } else {
                    batchFutures.add(exportTaskExecutor.submit(() -> itemMapService.exportItemList(params, finalCompanyCode, page)));
                }
            }
            log.info("开始写入excel");
            while (!batchFutures.isEmpty()) {
                Future<List<ExportItemListVo>> future = batchFutures.remove(0);
                excelWriter.write(future.get(30, TimeUnit.SECONDS), writeSheet);
            }
        }