【SpirngBoot】EasyExcel 多线程 + 分批次查询数据,逐步导出,降低内存占用,通用模板适用于所有的导出

  • 首先,添加EasyExcel依赖到pom.xml文件中:
<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>easyexcelartifactId>
    <version>2.2.10version>
dependency>

基于 EasyExcel 的通用模板,支持分页查询并多线程异步导出大批量数据,并且能够处理异常情况。你可以根据需要进行修改和优化

@RestController
public 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 <= totalPages; i++) {
            int page = i;
            executorService.submit(() -> {
                try {
					PageHelper.startPage(pageNum, pageSize);
                    List<Data> 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<Data> 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<Data> getDataList(int page, int pageSize);
}

@Service
public class DataService1 implements DataService {
   @Override
   public int getTotal() {
       // 实现获取总记录数的逻辑
   }

   @Override
   public List<Data> getDataList(int page, int pageSize) {
       // 实现获取数据列表的逻辑
   }
}

@Service
public class DataService2 implements DataService {
   @Override
   public int getTotal() {
       // 实现获取总记录数的逻辑
   }

   @Override
   public List<Data> getDataList(int page, int pageSize) {
       // 实现获取数据列表的逻辑
   }
}

在上面的示例中,DataService 是一个公共的接口,它定义了 getTotal()getDataList() 方法。DataService1DataService2 是两个不同的 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));
    }
}

@Service
public 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<Data> 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++;
        }
    }
}
  • 如需改成异步 + 多线程方式
@Service
public 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<Data> 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<Data> 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();
    }
}

以上思路即是针对数据量较大情况下处理方式,如有文件服务器可采用异步+多线程方式生成文件上传到文件服务器,用户点击下载弹出下载页面,用户下载文件服务中附件,此文件可重复下载,减轻相同的再次生成文件执行业务代码等流程

你可能感兴趣的:(技术问题,java,servlet,开发语言)