SpirngBootEasyExcel 多线程 + 分批次查询数据,逐步导出,降低内存占用,通用模板适用于所有的导出
首先,添加EasyExcel依赖到pom.xml文件中: com.alibaba easyexcel 2.2.10
基于 EasyExcel 的通用模板,支持分页查询并多线程异步导出大批量数据,并且能够处理异常情况。你可以根据需要进行修改和优化
@RestControllerpublic class ExportController { /** * 导出数据 * @param response */ @PostMapping("/export") public void export(HttpServletResponse response) { // 查询总数 int total = getTotal(); // 每页数据量 int pageSize = 1000; // 总页数 int totalPages = (total + pageSize - 1) / pageSize; // 导出文件名 String fileName = "data.xlsx"; // 设置响应头 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment;filename=" + fileName); // 创建 ExcelWriter 对象 ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build(); // 创建 Sheet 对象 WriteSheet writeSheet = EasyExcel.writerSheet(0, "Sheet1").build(); // 创建 CountDownLatch 对象 CountDownLatch countDownLatch = new CountDownLatch(totalPages); // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 分页查询并异步导出数据 for (int i = 1; i try {PageHelper.startPage(pageNum, pageSize); List dataList = getDataList(page, pageSize); // 写入数据 excelWriter.write(dataList, writeSheet); } catch (Exception e) { // 异常处理 e.printStackTrace(); } finally { // 计数器减一 countDownLatch.countDown(); } }); } // 等待所有线程执行完毕 try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } // 关闭 ExcelWriter 对象 excelWriter.finish(); // 关闭线程池 executorService.shutdown(); } /** * 获取总数 * @return */ private int getTotal() { // TODO: 查询总数 return 10000; } /** * 分页查询数据 * @param page * @param pageSize * @return */ private List getDataList(int page, int pageSize) { // TODO: 分页查询数据 return new ArrayList(); }}这个通用模板支持分页查询数据并异步导出,可以通过修改 getTotal() 和 getDataList() 方法来适应不同的业务场景。同时,它也支持多线程处理数据,可以通过修改 newFixedThreadPool() 方法的参数来控制线程池的大小。需要注意的是,在使用 inMemory 方式导出数据时,需要注意内存溢出的问题。
getTotal() 和 getDataList() 方法,您可以将这两个方法定义在一个公共的接口 如果您想在不同的 Service 中使用 getTotal() 和 getDataList() 方法,您可以将这两个方法定义在一个公共的接口中,然后在不同的 Service 中实现该接口。这样,您就可以在不同的 Service 中使用相同的方法名来获取总记录数和数据列表。 public interface DataService { int getTotal(); List getDataList(int page, int pageSize);}@Servicepublic class DataService1 implements DataService { @Override public int getTotal() { // 实现获取总记录数的逻辑 } @Override public List getDataList(int page, int pageSize) { // 实现获取数据列表的逻辑 }}@Servicepublic class DataService2 implements DataService { @Override public int getTotal() { // 实现获取总记录数的逻辑 } @Override public List getDataList(int page, int pageSize) { // 实现获取数据列表的逻辑 }}在上面的示例中,DataService 是一个公共的接口,它定义了 getTotal() 和 getDataList() 方法。DataService1 和 DataService2 是两个不同的 Service,它们都实现了 DataService 接口,并分别实现了 getTotal() 和 getDataList() 方法。在其他类中,您可以使用 DataService 接口的引用来调用这两个方法,而不需要关心具体的实现类。
异步导出和分批次查询数据的方式来提高导出效率
在 Controller 层中,定义一个导出接口,接口中传入需要导出的数据的查询条件
在 Service 层中,根据传入的查询条件,分批次查询需要导出的数据
将查询到的数据使用 EasyExcel 进行导出,导出时可以采用异步导出的方式,这样可以避免导出数据量过大导致的内存溢出问题
在导出完成后,将导出结果返回给前端
@RestController@RequestMapping("/export")public class ExportController { @Autowired private ExportService exportService; @GetMapping("/data") public void exportData(@RequestParam("param") String param, HttpServletResponse response) { // 设置导出文件名 String fileName = "data.xlsx"; // 设置响应头 response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-disposition", "attachment;filename=" + fileName); // 异步导出数据 CompletableFuture.runAsync(() -> exportService.exportData(param, response)); }}@Servicepublic class ExportServiceImpl implements ExportService { @Autowired private DataMapper dataMapper; @Override public void exportData(String param, HttpServletResponse response) { // 分批次查询数据 int pageSize = 1000; int pageNum = 1; boolean hasNextPage = true; while (hasNextPage) { PageHelper.startPage(pageNum, pageSize); List dataList = dataMapper.getDataList(param); if (CollectionUtils.isEmpty(dataList)) { hasNextPage = false; break; } // 使用 EasyExcel 进行导出try (OutputStream out = response.getOutputStream()) { // 导出数据 EasyExcel.write(out, DemoData.class).sheet("模板").doWrite(data);} catch (Exception e) { // 异常处理} pageNum++; } }} 如需改成异步 + 多线程方式 @Servicepublic class ExportServiceImpl implements ExportService { @Autowired private DataMapper dataMapper; @Override public void exportData(String param, HttpServletResponse response) {// 创建线程池ExecutorService executorService = Executors.newFixedThreadPool(4); // 分批次查询数据 int pageSize = 1000; int pageNum = 1; boolean hasNextPage = true; while (hasNextPage) { PageHelper.startPage(pageNum, pageSize); List dataList = dataMapper.getDataList(param); if (CollectionUtils.isEmpty(dataList)) { hasNextPage = false; break; } // 使用 EasyExcel 进行导出try (OutputStream out = response.getOutputStream()) {// 导出数据EasyExcel.write(out, DemoData.class).sheet("模板").doWrite(data);} catch (Exception e) {// 异常处理}executorService.submit(() -> {PageHelper.startPage(pageNum, pageSize);List dataList = dataMapper.getDataList(param);if (CollectionUtils.isEmpty(dataList)) {hasNextPage = false;break;}// 使用 EasyExcel 进行导出try (OutputStream out = response.getOutputStream()) {// 导出数据EasyExcel.write(out, DemoData.class).sheet("模板").doWrite(data);} catch (Exception e) {// 异常处理}}); pageNum++; }// 关闭线程池executorService.shutdown(); }}以上思路即是针对数据量较大情况下处理方式,如有文件服务器可采用异步+多线程方式生成文件上传到文件服务器,用户点击下载弹出下载页面,用户下载文件服务中附件,此文件可重复下载,减轻相同的再次生成文件执行业务代码等流程