一、背景
最近做了一个excel导出功能,最开始查阅资料,EasyPoi以及阿里出的EasyExcel都是不错的工具,二者对比,EasyPoi导出效率优于EasyExcel,但是前者要比后者更消耗内存。(这个对比后续我会单独做总结,这篇文章主讲使用EasyPoi的导出excel的使用)
二、导出步骤
(1)maven
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.1.0</version>
</dependency>
(2)定义实体
@Data
@ExcelTarget("msgClient")
public class MsgClient {
@Excel(name = "客户姓名")
private String clientName;
@Excel(name = "客户电话号码")
private String clientPhone;
@Excel(name = "创建人")
private String createBy;
@Excel(name = "ID")
private String id;
@Excel(name = "标记")
private String remark;
@Excel(name = "生日")
private Date birthday;
}
(3)定义导出的excel的样式
public class ExcelStyleUtil implements IExcelExportStyler {
private static final short STRING_FORMAT = (short) BuiltinFormats.getBuiltinFormat("TEXT");
private static final short FONT_SIZE_TEN = 10;
private static final short FONT_SIZE_ELEVEN = 11;
private static final short FONT_SIZE_TWELVE = 12;
private CellStyle headerStyle;
private CellStyle titleStyle;
private CellStyle styles;
public ExcelStyleUtil(Workbook workbook) {
this.init(workbook);
}
private void init(Workbook workbook) {
this.headerStyle = initHeaderStyle(workbook);
this.titleStyle = initTitleStyle(workbook);
this.styles = initStyles(workbook);
}
@Override
public CellStyle getHeaderStyle(short color) {
return headerStyle;
}
@Override
public CellStyle getTitleStyle(short color) {
return titleStyle;
}
@Override
public CellStyle getStyles(boolean parity, ExcelExportEntity excelExportEntity) {
return styles;
}
@Override
public CellStyle getStyles(Cell cell, int dataRow, ExcelExportEntity excelExportEntity, Object obj, Object data) {
return getStyles(true, excelExportEntity);
}
@Override
public CellStyle getTemplateStyles(boolean b, ExcelForEachParams excelForEachParams) {
return null;
}
private CellStyle initHeaderStyle(Workbook workbook) {
CellStyle style = getBaseCellStyle(workbook);
style.setFont(getFont(workbook, FONT_SIZE_TWELVE, true));
return style;
}
private CellStyle initTitleStyle(Workbook workbook) {
CellStyle style = getBaseCellStyle(workbook);
style.setFont(getFont(workbook, FONT_SIZE_ELEVEN, false));
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
return style;
}
private CellStyle initStyles(Workbook workbook) {
CellStyle style = getBaseCellStyle(workbook);
style.setFont(getFont(workbook, FONT_SIZE_TEN, false));
style.setDataFormat(STRING_FORMAT);
return style;
}
private CellStyle getBaseCellStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setWrapText(true);
return style;
}
private Font getFont(Workbook workbook, short size, boolean isBold) {
Font font = workbook.createFont();
font.setFontName("宋体");
font.setBold(isBold);
font.setFontHeightInPoints(size);
return font;
}
}
(4)定义Excel导出工具类
@Slf4j
@Component
public class MyExcelExportUtil {
@Resource
private IExcelExportServer excelExportServer;
private static final Integer EXPORT_EXCEL_BASE_MAX_NUM = 100000;
public static Workbook getWorkbook(String title, String sheetName, Class<?> object, List<?> list, ExcelType excelType) {
if (list == null) {
list = new ArrayList<>();
}
if (list.size() > EXPORT_EXCEL_BASE_MAX_NUM) {
title = "导出数据行数超过:" + EXPORT_EXCEL_BASE_MAX_NUM + "条,无法导出!";
list = new ArrayList<>();
}
ExportParams exportParams = new ExportParams(title, sheetName, excelType);
exportParams.setStyle(ExcelStyleUtil.class);
exportParams.setHeight((short) 8);
return ExcelExportUtil.exportExcel(exportParams, object, list);
}
public Workbook getWorkbook(String title, String sheetName, Class<?> object, Object queryParams, ExcelType excelType) {
ExportParams exportParams = new ExportParams(title, sheetName, excelType);
exportParams.setStyle(ExcelStyleUtil.class);
exportParams.setHeight((short) 6);
return ExcelExportUtil.exportBigExcel(exportParams, object, excelExportServer, queryParams);
}
public static Workbook getWorkbook(String path, Map<String, Object> map) {
TemplateExportParams params = new TemplateExportParams(path);
params.setStyle(ExcelStyleUtil.class);
return ExcelExportUtil.exportExcel(params, map);
}
public static void exportExcel(Workbook workbook, String fileName, HttpServletResponse response) {
try (OutputStream out = response.getOutputStream()) {
String name = URLEncoder.encode(fileName, "UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment;filename=" + name + ".xlsx");
workbook.write(out);
} catch (IOException e) {
log.error("文件导出异常,详情如下:", e);
throw new RuntimeException("文件导出异常");
} finally {
try {
if (workbook != null) {
workbook.close();
}
} catch (IOException e) {
log.error("文件导出异常,详情如下:", e);
}
}
}
}
(5)Controller
普通导出测试: EasyPoi自带了小数据量导出的方法,当小于10w条数使用XSSFWorkbook,但是其在创建Excel、或者说写Excel的时候内存开销是很大的。当超过10w条数据使用SXSSFWorkbook。SXSSFWorkbook是apache的POI项目推出了一个实现大数据量的流式版本XSSFWorkbook,对于编写非常大的文件时不会耗尽内存,因为在任何时候只有行的可配置部分保存在内存中,例如合并区域,注释等。 查看ExcelExportUtil.exportExcel底层的代码发现:
@RestController
public class ExcelExportController {
@Resource
private MyExcelExportUtil myExcelExportUtil;
@Resource
private IExcelExportServer exportBigExcel;
@GetMapping(value = "/export")
public void test(HttpServletResponse response) {
List<Object> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
MsgClient client = new MsgClient();
client.setBirthday(new Date());
client.setClientName("小明xxxsxsxsxsxsxsxsxsxsx" + i);
client.setClientPhone("18797" + i);
client.setCreateBy("JueYue");
client.setId("1" + i);
client.setRemark("测试" + i);
list.add(client);
}
try {
Date start = new Date();
Workbook workbook = myExcelExportUtil.getWorkbook("计算机一班学生", "学生", MsgClient.class, list, ExcelType.XSSF);
MyExcelExportUtil.exportExcel(workbook, String.valueOf(System.currentTimeMillis()), response);
System.out.println("export:" + (new Date().getTime() - start.getTime()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
大数据量分批导出: 数据量较大的情况,一次性从数据库查这么多数据会给数据库带来很大的压力,此时可以使用数据分页导出的方法来减轻对数据库操作的压力。
@GetMapping(value = "/bigDataExport")
public void bigDataExport(HttpServletResponse response) throws IOException {
Date start = new Date();
ExportParams params = new ExportParams("大数据测试", "测试");
Workbook workbook = ExcelExportUtil.exportBigExcel(params, MsgClient.class, exportBigExcel, new Object());
MyExcelExportUtil.exportExcel(workbook, String.valueOf(System.currentTimeMillis()), response);
System.out.println("bigDataExport:" + (new Date().getTime() - start.getTime()));
}
IExcelExportServer的实现类: 查询50w条数据:
@Service
public class IExcelExportServerImpl implements IExcelExportServer {
@Override
public List<Object> selectListForExcelExport(Object obj, int page) {
int pageSize = 10000;
List<Object> list = new ArrayList<>();
for (int i = 0; i < 500000; i++) {
MsgClient client = new MsgClient();
client.setBirthday(new Date());
client.setClientName("小明" + i);
client.setClientPhone("18797" + i);
client.setCreateBy("JueYue");
client.setId("1" + i);
client.setRemark("测试" + i);
list.add(client);
}
List partList = new ArrayList();
if (page * pageSize <= 500000) {
partList = list.subList((page - 1) * pageSize, page * pageSize);
}
return partList;
}
}
说明:这里为什么要通过一个实现类去查询数据呢?因为要做分页,循环查出每页的数据,每次查询都要调用实现类中的查询方法。 进源码看一下:
public Workbook exportBigExcel(IExcelExportServer server, Object queryParams) {
int page = 1;
int var6 = page + 1;
for(List list = server.selectListForExcelExport(queryParams, page); list != null && list.size() > 0; list = server.selectListForExcelExport(queryParams, var6++)) {
this.appendData(list);
}
return this.closeExportBigExcel();
}
(6)测试
普通导出测试: 启动项目,浏览器输入:http://localhost:8080/export 导出结果: excel内容展示: 大数据量分页导出测试: 启动项目,浏览器输入:http://localhost:8080/bigDataExport 导出结果: 从0计数的所以是50w条数据。 第一个“测试”sheet不知道为啥是9w条数据,我看底层代码限制的是10w条数据(见下图)有知道的小伙伴麻烦留个言哈。
三、样式整改-表格宽度调节
通过查看底层源码,表格的宽度被写死了,宽度自适应的属性也不能设置了,于是乎,只能自己改源码去调节表格宽度了,如果想改成自适应的那种,改动太多了,不建议这么做了。 整改如下: 公共类MyExcelExportUtil中的修改: 自定义createExcel方法:
public static Workbook createExcel(ExportParams entity, Class<?> pojoClass, Collection<?> dataSet) {
Workbook workbook = getWorkbook(entity.getType(), dataSet.size());
(new MyExcelExportService()).createSheet(workbook, entity, pojoClass, dataSet);
return workbook;
}
private static Workbook getWorkbook(ExcelType type, int size) {
if (ExcelType.HSSF.equals(type)) {
return new HSSFWorkbook();
} else {
return size < USE_SXSSF_LIMIT ? new XSSFWorkbook() : new SXSSFWorkbook();
}
}
创建MyExcelExportService文件:
@Slf4j
public class MyExcelExportService {
private static double EXCEL_WIDTH = 25.0D;
public void createSheet(Workbook workbook, ExportParams entity, Class<?> pojoClass, Collection<?> dataSet) {
if (log.isDebugEnabled()) {
log.debug("Excel export start ,class is {}", pojoClass);
log.debug("Excel version is {}", entity.getType().equals(ExcelType.HSSF) ? "03" : "07");
}
if (workbook != null && entity != null && pojoClass != null && dataSet != null) {
try {
List<ExcelExportEntity> excelParams = new ArrayList();
Field[] fileds = PoiPublicUtil.getClassFields(pojoClass);
ExcelTarget etarget = pojoClass.getAnnotation(ExcelTarget.class);
String targetId = etarget == null ? null : etarget.value();
ExcelExportService excelExportService = new ExcelExportService();
excelExportService.getAllExcelField(entity.getExclusions(), targetId, fileds, excelParams, pojoClass, null, (ExcelEntity) null);
excelParams.stream().forEach(x -> x.setWidth(EXCEL_WIDTH));
excelExportService.createSheetForMap(workbook, entity, excelParams, dataSet);
} catch (Exception var9) {
log.error(var9.getMessage(), var9);
throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, var9.getCause());
}
} else {
throw new ExcelExportException(ExcelExportEnum.PARAMETER_ERROR);
}
}
}
效果图:
四、总结:
本篇文章主要讲了通过easypoi如何导出excel的实现以及修改表格的宽度,感兴趣的小伙伴可以看看它的源码的实现,这样理解起来更加深刻。 需要更多关于easypoi功能的可以查看官方文档: easypoi官方文档:http://easypoi.mydoc.io 百万级数据导出到excel我会在下一篇进行讲解。 可能有说的不准确的地方,欢迎指正。
|