一、技术选型
ava解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到KB级别,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便
easyExcel官方GitHub地址:内有详细的讲解
小编最近在工作中刚好遇到了这么一个需求,将一系列数据导出为Excel表格。然后就开始百度,就看到了上面这段话。 经过小编对 poi 和 easyExcel 的demo的对比,决定使用easyExcel实现这个需求。
二、实现过程
1、导入依赖
建议去GitHub查看最新版本号:
<!-- 阿里开源EXCEL -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>1.1.2-beta5</version>
</dependency>
2、编写工具类 EasyExcelUtil
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Font;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.TableStyle;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.hanclouds.teamwork.entity.EasyExcelParams;
import org.apache.poi.ss.usermodel.IndexedColors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author MouFangCai
* @date 2019/7/23 11:48
*/
public class EasyExcelUtil {
/**
* 下载EXCEL文件2007版本
*
* @throws IOException IO异常
*/
public static void exportExcel2007Format(EasyExcelParams excelParams) throws IOException {
exportExcel(excelParams, ExcelTypeEnum.XLSX);
}
/**
* 下载EXCEL文件2003版本
*
* @throws IOException IO异常
*/
public static void exportExcel2003Format(EasyExcelParams excelParams) throws IOException {
exportExcel(excelParams, ExcelTypeEnum.XLS);
}
/**
* 根据参数和版本枚举导出excel文件
*
* @param excelParams 参数实体
* @param typeEnum excel类型枚举
* @throws IOException
*/
private static void exportExcel(EasyExcelParams excelParams, ExcelTypeEnum typeEnum) throws IOException {
HttpServletResponse response = excelParams.getResponse();
ServletOutputStream out = response.getOutputStream();
// ExcelWriter提供了多种构造方式,可自行查看选择所需要的
ExcelWriter writer = new ExcelWriter(null, out, typeEnum,
true, excelParams.getWriteHandler());
// 设置web下载等的信息
prepareResponds(response, typeEnum);
// 创建一个sheet
Sheet sheet = new Sheet(1, 0, excelParams.getDataModelClazz());
sheet.setSheetName(excelParams.getSheetName());
// 设置列宽 设置每列的宽度
Map<Integer,Integer> columnWidth = new HashMap<>(6);
columnWidth.put(0,6666);
columnWidth.put(1,5000);
columnWidth.put(2,15000);
columnWidth.put(3,3000);
columnWidth.put(4,20000);
columnWidth.put(5,10000);
sheet.setColumnWidthMap(columnWidth);
sheet.setAutoWidth(Boolean.TRUE);
// 用于设置 表格样式
sheet.setTableStyle(createTableStyle());
// 写入Excel中,也提供了多个重载方法,根据需要选择
writer.write(excelParams.getData(), sheet);
// 根据数据展示需要,用于合并单元格
List<int[]> mergeList = excelParams.getMergeList();
if (mergeList != null && mergeList.size() > 0){
for (int[] arr : mergeList) {
// 待合并的单元格参数:开始的行数,结束的行数,开始的列数,结束的列数
writer.merge(arr[0], arr[1], arr[2], arr[3]);
}
}
writer.finish();
out.flush();
}
/**
* 将文件输出到浏览器(导出文件)
*
* @param response 响应
* @param typeEnum excel类型
*/
private static void prepareResponds(HttpServletResponse response,ExcelTypeEnum typeEnum) throws UnsupportedEncodingException {
String fileName = new String((new SimpleDateFormat("MMddHHmm").format(new Date()))
.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
response.setContentType("multipart/form-data");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + typeEnum.getValue());
}
/**
* 表格样式:目前easyExcel的样式调整,仅支持表头和内容两部分别统一设置
* @return
*/
private static TableStyle createTableStyle(){
TableStyle tableStyle = new TableStyle();
tableStyle.setTableHeadBackGroundColor(IndexedColors.GREY_50_PERCENT);
tableStyle.setTableContentBackGroundColor(IndexedColors.GREY_25_PERCENT);
Font contentFont = new Font();
contentFont.setFontName("黑体");
contentFont.setFontHeightInPoints((short)12);
tableStyle.setTableContentFont(contentFont);
Font headFont = new Font();
headFont.setFontName("黑体");
headFont.setFontHeightInPoints((short)10);
tableStyle.setTableHeadFont(headFont);
return tableStyle;
}
}
writer.merge(arr[0], arr[1], arr[2], arr[3]); 关于合并单元的这个merge方法
public ExcelWriter merge(int firstRow, int lastRow, int firstCol, int lastCol) { this.excelBuilder.merge(firstRow, lastRow, firstCol, lastCol); return this; }
通过查看方法详情,即可理解:开始合并的行数,结束合并的行数,开始合并的列数,结束合并的列数
需要注意的是:对应的 last 的值必须大于等于 first 的值
3、公用参数类 EasyExcelParams
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Table;
import com.hanclouds.teamwork.util.MyWriteHandler;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* @author MouFangCai
* @date 2019/7/23 20:18
*/
public class EasyExcelParams {
/**
* 表格样式:使用
*/
private MyWriteHandler writeHandler;
/**
* 表格样式:未使用
*/
private Table table;
/**
* 需合并的单元格参数配置集合 list
*/
private List<int[]> mergeList;
/**
* excel文件名(不带拓展名)
*/
private String excelNameWithoutExt;
/**
* sheet名称
*/
private String sheetName;
/**
* 是否需要表头
*/
private boolean needHead = true;
/**
* 数据
*/
private List<? extends BaseRowModel> data;
/**
* 数据模型类型
*/
private Class<? extends BaseRowModel> dataModelClazz;
/**
* 响应
*/
private HttpServletResponse response;
public EasyExcelParams() {
}
public MyWriteHandler getWriteHandler() {
return writeHandler;
}
public void setWriteHandler(MyWriteHandler writeHandler) {
this.writeHandler = writeHandler;
}
public Table getTable() {
return table;
}
public void setTable(Table table) {
this.table = table;
}
public List<int[]> getMergeList() {
return mergeList;
}
public void setMergeList(List<int[]> mergeList) {
this.mergeList = mergeList;
}
public String getExcelNameWithoutExt() {
return excelNameWithoutExt;
}
public void setExcelNameWithoutExt(String excelNameWithoutExt) {
this.excelNameWithoutExt = excelNameWithoutExt;
}
public String getSheetName() {
return sheetName;
}
public void setSheetName(String sheetName) {
this.sheetName = sheetName;
}
public boolean isNeedHead() {
return needHead;
}
public void setNeedHead(boolean needHead) {
this.needHead = needHead;
}
public List<? extends BaseRowModel> getData() {
return data;
}
public void setData(List<? extends BaseRowModel> data) {
this.data = data;
}
public Class<? extends BaseRowModel> getDataModelClazz() {
return dataModelClazz;
}
public void setDataModelClazz(Class<? extends BaseRowModel> dataModelClazz) {
this.dataModelClazz = dataModelClazz;
}
public HttpServletResponse getResponse() {
return response;
}
public void setResponse(HttpServletResponse response) {
this.response = response;
}
}
4、表格样式实体类 MyWriteHandler
关于使用easyExcel去进行表格样式的设置,个人感觉对于复杂的样式成本是比较的,所以小编这里的样式,仅仅只有边框、水平居中、垂直居中、内容自适应。而且整个表格内容的格式是一样的。
package com.hanclouds.teamwork.util;
import com.alibaba.excel.event.WriteHandler;
import org.apache.poi.ss.usermodel.*;
/**
* @author MouFangCai
* @date 2019/7/28 18:06
*/
public class MyWriteHandler implements WriteHandler {
private CellStyle cellStyle;
@Override
public void sheet(int i, Sheet sheet) {
Workbook workbook = sheet.getWorkbook();
// 创建样式
cellStyle = workbook.createCellStyle();
}
@Override
public void row(int i, Row row) {
}
@Override
public void cell(int i, Cell cell) {
if (cell.getRowIndex() >0) {
createStyle();
cell.setCellStyle(this.cellStyle);
}
}
private void createStyle() {
// 填充色:和EasyExcelUtil里的createTableStyle效果一样
// cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
// cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.BLUE.getIndex());
// cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 下边框
cellStyle.setBorderBottom(BorderStyle.THIN);
// 左边框
cellStyle.setBorderLeft(BorderStyle.THIN);
// 右边框
cellStyle.setBorderRight(BorderStyle.THIN);
// 上边框
cellStyle.setBorderTop(BorderStyle.THIN);
// 设置自动换行
cellStyle.setWrapText(true);
// 水平对齐方式
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cellStyle.setFillBackgroundColor((short)22);
// 垂直对齐方式
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
}
}
5、数据实体类(也是表头)BaseRowModel
注意,必须继承** BaseRowModel**
** @ExcelProperty(value = "部门",index = 0) 该注解的作用就是:声明表头名,和对应的位置,index=0表示在列数“第一列”**
package com.hanclouds.teamwork.entity;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;
/**
* @author MouFangCai
* @date 2019/7/24 9:18
*/
public class WeeklyExportDto extends BaseRowModel {
@ExcelProperty(value = "部门",index = 0)
private String groupName;
@ExcelProperty(value = "项目名称",index = 1)
private String projectName;
@ExcelProperty(value = "任务名",index = 2)
private String weeklyTaskName;
@ExcelProperty(value = "责任人",index = 3)
private String dutyUserName;
@ExcelProperty(value = "完成情况、下周计划",index = 4)
private String detail;
@ExcelProperty(value = "任务描述",index = 5)
private String taskDescription;
public WeeklyExportDto() {
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getWeeklyTaskName() {
return weeklyTaskName;
}
public void setWeeklyTaskName(String weeklyTaskName) {
this.weeklyTaskName = weeklyTaskName;
}
public String getDutyUserName() {
return dutyUserName;
}
public void setDutyUserName(String dutyUserName) {
this.dutyUserName = dutyUserName;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public String getTaskDescription() {
return taskDescription;
}
public void setTaskDescription(String taskDescription) {
this.taskDescription = taskDescription;
}
}
6、主程序
数据结构说明:
参考:8、导出的Excel效果
待导出的数据结构如下:
---有多个分组:List<WeeklyGroupRes>
----一个分组下,有多个项目: List<WeeklyProjectDto> weeklyProjectList = weeklyGroupRes.getWeeklyProjectList();
----一个项目下,有多个任务: List<WeeklyItemDto> itemDtoList = weeklyProjectDto.getItemDtoList();同一个分组,需合并单元格;同一个项目,需合并单元格。
@GetMapping("/export")
public boolean exportExcel( HttpServletResponse response) {
// 先从数据库 获取 需要导出的数据(此处自查)
List<WeeklyGroupRes> dataList = new ArrayList<>();
// mergeList 用于存放合并单元格的配置信息
// int[] 数据存放4个参数,分别对应merge方法的参数
// (int firstRow, int lastRow, int firstCol, int lastCol)
List<int[]> mergeLis =new ArrayList<>();
// 用于存放 写入Excel的数据
List<WeeklyExportDto> resultList = new ArrayList<>();
// 用于生成merge
int lastRow = 0;
// 遍历处理需要导出的数据,下面就是小编对数据结构的遍历处理,可以忽略
for (WeeklyGroupRes weeklyGroupRes : dataList) {
int[] groupInt = new int[4];
groupInt[0] = lastRow + 1;
List<WeeklyProjectDto> weeklyProjectList = weeklyGroupRes.getWeeklyProjectList();
for (WeeklyProjectDto weeklyProjectDto : weeklyProjectList) {
int[] projectInt = new int[4];
projectInt[0] = lastRow + 1;
List<WeeklyItemDto> itemDtoList = weeklyProjectDto.getItemDtoList();
for (WeeklyItemDto weeklyItemDto : itemDtoList) {
String detail = thisComplete + weeklyItemDto.getComplete()
+ "\n" + nextPlan + weeklyItemDto.getPlan();
Task task = taskMap.get(weeklyItemDto.getTaskId());
WeeklyExportDto exportDto = new WeeklyExportDto();
exportDto.setGroupName(weeklyGroupRes.getMemberGroupName());
exportDto.setProjectName(weeklyProjectDto.getProjectName());
exportDto.setWeeklyTaskName(weeklyItemDto.getWeeklyTaskName());
exportDto.setDutyUserName(weeklyItemDto.getUserNickName());
exportDto.setDetail(detail);
exportDto.setTaskDescription(task.getDescription());
resultList.add(exportDto);
}
lastRow = lastRow + itemDtoList.size();
projectInt[1] = lastRow;
projectInt[2] = 1;
projectInt[3] = 1;
if (projectInt[0] != projectInt[1]) {
mergeLis.add(projectInt);
}
}
groupInt[1] = lastRow;
groupInt[2] = 0;
groupInt[3] = 0;
if (groupInt[0] != groupInt[1]) {
mergeLis.add(groupInt);
}
}
// 设置各种参数,用于导出Excel
EasyExcelParams easyExcelParams = new EasyExcelParams();
easyExcelParams.setSheetName("导出Excel");
easyExcelParams.setResponse(response);
easyExcelParams.setData(resultList);
easyExcelParams.setDataModelClazz(WeeklyExportDto.class);
easyExcelParams.setNeedHead(false);
easyExcelParams.setMergeList(mergeLis);
easyExcelParams.setWriteHandler(new MyWriteHandler());
try {
EasyExcelUtil.exportExcel2007Format(easyExcelParams);
} catch (IOException e) {
e.printStackTrace();
throw new HanCloudsException(CommonErrorCode.INTERNAL_SERVER_ERROR.getErrorCode(),
"Export failed. Please try again");
}
return true;
}
7、运行程序,效果如下:
8、导出的Excel效果