- 首先,添加EasyExcel依赖到pom.xml文件中:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.10</version>
</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()
方法。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));
}
}
@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();
}
}
以上思路即是针对数据量较大情况下处理方式,如有文件服务器可采用异步+多线程方式生成文件上传到文件服务器,用户点击下载弹出下载页面,用户下载文件服务中附件,此文件可重复下载,减轻相同的再次生成文件执行业务代码等流程