目录
前言
一、EasyExcel简介
1、网站
二、EasyExcel使用
1、相关依赖
2、实现代码
2.1 实体类
2.2 服务层
2.2.1 服务接口类
2.2.2 实现类
2.3 控制层
2.4 相关工具类
2.4.1 导出Util
2.4.2 Excel自定义样式拦截器
2.4.3 Excel单元格拦截器
2.4.4 Excel自定义列宽拦截器
2.4.5 性别转换器
2.4.6 性别枚举
2.5 结果
三、EasyExcel常用注解
1、@ExcelProperty
2、@ColumnWith
3、@ContentFontStyle
4、@ContentLoopMerge和@OnceAbsoluteMerge
4.1 @ContentLoopMerge
4.2 @OnceAbsoluteMerge
5、@ContentRowHeight
6、@ContentStyle
7、@HeadFontStyle
8、@HeadRowHeight
9、@HeadStyle
10、@ExcelIgnore
11、@ExcelIgnoreUnannotated
12、@NumberFormat
13、@DateTimeFormat
相信在大部分的web项目中都会有导出导入Excel的需求,今天我们就来看看如何用阿里的EasyExcel组件去实现导出Excel文件的需求。之前我也写过几篇使用Apache POI导出Excel的文章,有兴趣的小伙伴可以去瞅瞅,链接也给大家放出来了:
1、Apache POI导出excel(一):单sheet
2、Apache POI导出excel(二):多个sheet
3、JAVA 导出excel(三):导出zip压缩包
Java解析、生成Excel比较有名的框架有Apache POI、JXL。但他们都存在一个严重的问题就是非常的耗内存,POI有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
EasyExcel重写了POI对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用EasyExcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便。
pom.xml
com.alibaba
easyexcel
3.2.1
poi-ooxml-schemas
org.apache.poi
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.test.java.converter.GenderConverter;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 学生类
*/
@Data // lombok注解,会生成getter/setter方法
@ExcelIgnoreUnannotated // 没加导出注解的字段,不导出
public class StudentVo implements Serializable {
/*** 用户ID*/
private Long userId;
/*** 姓名*/
@ExcelProperty(value = "姓名")
@ColumnWidth(10)
private String studentName;
/*** 年龄*/
@ExcelProperty(value = "年龄")
private Integer age;
/*** 手机号*/
@ExcelProperty(value = "手机号")
private String phone;
/*** 性别(1男 2女)*/
@ExcelProperty(value = "性别", converter = GenderConverter.class)
private Integer gender;
/*** 生日*/
@ExcelProperty(value = "生日")
private String birthday;
/*** 分数*/
@ExcelProperty(value = "分数")
@NumberFormat(value = "###.#") // 数字格式化,保留1位小数
private BigDecimal score;
/*** 创建时间*/
@ExcelProperty(value = "创建时间")
@DateTimeFormat("yyyy-MM-dd")
private Date createTime;
}
// 导出学生信息
List exportStudent();
@Override
public List exportStudent() {
List list = new ArrayList<>();
// 我这里使用for循环 创造了10天测试数据,实际的业务场景肯定是从数据库中查出需要导出的数据
for (int i = 1; i <= 10; i++) {
StudentVo student = new StudentVo();
student.setUserId((long) i);
student.setStudentName("王" + i);
student.setAge(18 + i);
student.setPhone("1305678111" + i);
if (i % 2 == 0) {
student.setGender(2);
} else {
student.setGender(1);
}
student.setBirthday("1997-01-01");
student.setCreateTime(new Date());
list.add(student);
}
return list;
}
/**
* 导出学生信息
*/
@GetMapping("/exportStudent")
public void exportStudent(HttpServletResponse response) {
List list = userService.exportStudent();
// 指定列导出
String column = "studentName,age,phone";// 定义无需导出的列字段
if (StringUtils.isNotEmpty(column)) {
List columns = Arrays.asList(column.split(","));
ExportUtil.exportExcel(response, StudentVo.class, "学生信息", list, columns);
} else {
ExportUtil.exportExcel(response, StudentVo.class, "学生信息", list);
}
}
PS:我这里写了两个导出方法。一个是正常导出全部;另一个是指定列导出,只需传入无需导出的列字段集合,导出的时候就会进行过滤导出。
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Excel工具类
*/
public class ExportUtil extends EasyExcelFactory {
public ExportUtil() {
}
/**
* 导入Excel
*/
public static void importExcel(InputStream inputStream, Class> head, ReadListener readListener) {
read(inputStream, head, readListener).sheet().doRead();
}
/**
* 导出Excel(全部)
*
* @param response 响应
* @param clazz 表头数据
* @param fileName 文件名
* @param list 需要导出的数据
*/
public static void exportExcel(HttpServletResponse response, Class> clazz, String fileName, List> list) {
baseExportExcel(response, clazz, fileName, list, new ArrayList<>());
}
/**
* 导出Excel(指定列导出)
*
* @param response 响应
* @param clazz 表头数据
* @param fileName 文件名
* @param list 需要导出的数据
* @param excludeColumns 过滤导出的字段名
*/
public static void exportExcel(HttpServletResponse response, Class> clazz, String fileName, List> list, List excludeColumns) {
baseExportExcel(response, clazz, fileName, list, excludeColumns);
}
public static void baseExportExcel(HttpServletResponse response, Class> clazz, String fileName, List> list, List excludeColumns) {
try {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
String name = format.format(new Date());
String filename = URLEncoder.encode(name + fileName + ".xlsx", "UTF-8");
response.setCharacterEncoding("utf-8");
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", "attachment;filename=" + filename);
ExcelWriter excelWriter = write(response.getOutputStream()).registerWriteHandler(new ExcelColumnWidthHandler()).registerWriteHandler(new ExcelSheetWriteHandler()).registerWriteHandler(new ExcelStyleHandler()).excludeColumnFieldNames(excludeColumns).build();
WriteSheet writeSheet = writerSheet(0, fileName).head(clazz).build();
excelWriter.write(list, writeSheet);
excelWriter.finish();
} catch (Exception var8) {
throw new RuntimeException("导出" + fileName + "失败");
}
}
/**
* 导出Excel(多个sheet导出)
*
* @param response 响应
* @param fileName 文件名
*/
public static ExcelWriter exportExcels(HttpServletResponse response, String fileName) {
try {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
String name = format.format(new Date());
String filename = URLEncoder.encode(name + fileName + ".xlsx", "UTF-8");
response.setCharacterEncoding("utf-8");
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", "attachment;filename=" + filename);
return write(response.getOutputStream()).registerWriteHandler(new ExcelColumnWidthHandler()).registerWriteHandler(new ExcelSheetWriteHandler()).registerWriteHandler(new ExcelStyleHandler()).build();
} catch (Exception var3) {
throw new RuntimeException("导出" + fileName + "失败");
}
}
/**
* 创建工作表sheet
*
* @param sheetNo 工作表编号
* @param sheetName 工作表名称
* @param clazz 表头数据
*/
public static WriteSheet createSheet(Integer sheetNo, String sheetName, Class> clazz) {
return writerSheet(sheetNo, sheetName).head(clazz).build();
}
}
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.AbstractVerticalCellStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
/**
* Excel自定义样式拦截器
*/
public class ExcelStyleHandler extends AbstractVerticalCellStyleStrategy {
private static final String FONT_NAME = "等线";
private static final Integer[] COLUMN_INDEX = new Integer[]{1, 2, 3, 4, 7, 8, 9};
@Override
protected WriteCellStyle contentCellStyle(Head head) {
// 内容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 背景白色
contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
WriteFont contentWriteFont = new WriteFont();
// 字体大小
contentWriteFont.setFontHeightInPoints((short) 12);
// 字体样式
contentWriteFont.setFontName(FONT_NAME);
contentWriteCellStyle.setWriteFont(contentWriteFont);
return contentWriteCellStyle;
}
}
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.xssf.streaming.SXSSFSheet;
/**
* Excel单元格拦截器
*/
public class ExcelSheetWriteHandler implements SheetWriteHandler {
// 设置100列column
private static final Integer COLUMN = 100;
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
for(int i = 0; i < COLUMN; ++i) {
// 设置为文本格式
SXSSFSheet sxssfSheet = (SXSSFSheet)writeSheetHolder.getSheet();
CellStyle cellStyle = writeWorkbookHolder.getCachedWorkbook().createCellStyle();
// 49为文本格式
cellStyle.setDataFormat((short)49);
// i为列,一整列设置为文本格式
sxssfSheet.setDefaultColumnStyle(i, cellStyle);
}
}
}
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;
import java.util.List;
/**
* Excel自定义列宽拦截器
*/
public class ExcelColumnWidthHandler extends AbstractColumnWidthStyleStrategy {
@Override
protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
if (Boolean.TRUE.equals(isHead)) {
int columnWidth = cell.getStringCellValue().length();
columnWidth = Math.max(columnWidth * 2, 20);
if (columnWidth > 255) {
columnWidth = 255;
}
writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
}
}
}
2.4.2、2.4.3和2.4.4这三个拦截器类,主要是用于自定义导出Excel的一些样式,小伙伴也可以根据自己的需求,进行优化修改。
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.test.java.base.GenderEnum;
/**
* 性别转换器
*/
public class GenderConverter implements Converter {
@Override
public Class> supportJavaTypeKey() {
return Integer.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
// 读取Excel文件时将string转换为integer(导入)
@Override
public Integer convertToJavaData(ReadConverterContext> context) {
return GenderEnum.convert(context.getReadCellData().getStringValue()).getValue();
}
// 写入Excel文件时将integer转换为string(导出)
@Override
public WriteCellData> convertToExcelData(WriteConverterContext context) {
return new WriteCellData<>(GenderEnum.convert(context.getValue()).getDescription());
}
}
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.stream.Stream;
/**
* 性别枚举
*/
@Getter
@AllArgsConstructor
public enum GenderEnum {
/**
* 未知
*/
UNKNOWN(0, "未知"),
/**
* 男性
*/
MALE(1, "男性"),
/**
* 女性
*/
FEMALE(2, "女性");
private final Integer value;
@JsonFormat
private final String description;
public static GenderEnum convert(Integer value) {
return Stream.of(values())
.filter(bean -> bean.value.equals(value))
.findAny()
.orElse(UNKNOWN);
}
public static GenderEnum convert(String description) {
return Stream.of(values())
.filter(bean -> bean.description.equals(description))
.findAny()
.orElse(UNKNOWN);
}
}
导出全部:
导出指定列:
必要的一个字段注解,注解中有三个参数value
,index
,converter
分别代表列名,列序号,数据转换方式,通常不用设置converter
1.value 通过标题文本对应
2.index 通过文本行号对应
3.converter 转换器,通常入库和出库转换使用,如性别入库0和1,出库男和女
示例:
public class StudentVo {
/*** 姓名*/
@ExcelProperty(value = "姓名", index = 1)
private String studentName;
/*** 手机号*/
@ExcelProperty(value = "手机号", index = 2)
private String phone;
/*** 性别(1男 2女)*/
@ExcelProperty(value = "性别", index = 3,converter = GenderConverter.class)
private Integer gender;
}
设置列宽度,只有一个参数value,value的单位是字符长度,最大可以设置255个字符,因为一个excel单元格最大可以写入的字符个数就是255个字符。
示例:
public class StudentVo implements Serializable {
/*** 姓名*/
@ExcelProperty(value = "姓名")
@ColumnWidth(10)
private String studentName;
}
用于设置单元格内容字体格式的注解。
参数 | 含义 |
---|---|
fontName | 字体名称 |
fontHeightInPoints | 字体高度 |
italic | 是否斜体 |
strikeout | 是否设置删除水平线 |
color | 字体颜色 |
typeOffset | 偏移量 |
underline | 下划线 |
bold | 是否加粗 |
charset | 编码格式 |
相同点:这两个都是用于设置合并单元格的注解;它们都是按指定行数和指定列数去合并,并不能实现内容相同的合并。
不同点:@ContentLoopMerge是标注在字段上;@OnceAbsoluteMerge是标注在类上。
参数 | 含义 |
---|---|
eachRow | 指定行数 |
columnExtend | 指定列数 |
示例:
@Data
public class Demo implements Serializable {
@ExcelProperty(value = "商户名称", index = 0)
private String appName;
@ExcelProperty(value = "城市名称", index = 1)
@ContentLoopMerge(eachRow = 2, columnExtend = 3)
private String cityName;
@ExcelProperty(value = "区域名称", index = 2)
private String regionName;
@ExcelProperty(value = "商圈名称", index = 3)
private String businessAreaName;
@ExcelProperty(value = "楼盘名称", index = 4)
private String gardenName;
@ExcelProperty(value = "楼栋名称", index = 5)
private String buildingName;
@ExcelProperty(value = "单元名称", index = 6)
private String unitName;
@ExcelProperty(value = "价格", index = 7)
private Integer price;
}
参数 | 含义 |
---|---|
firstRowIndex | 指定合并的第一行索引 |
lastRowIndex | 指定合并的最后一行索引 |
firstColumnIndex | 指定合并的第一列索引 |
示例:
@Data
@OnceAbsoluteMerge(firstRowIndex = 1, lastRowIndex = 3 , firstColumnIndex = 1 , lastColumnIndex = 3)
public class Demo implements Serializable {
@ExcelProperty(value = "商户名称", index = 0)
private String appName;
@ExcelProperty(value = "城市名称", index = 1)
private String cityName;
@ExcelProperty(value = "区域名称", index = 2)
private String regionName;
@ExcelProperty(value = "商圈名称", index = 3)
private String businessAreaName;
@ExcelProperty(value = "楼盘名称", index = 4)
private String gardenName;
@ExcelProperty(value = "楼栋名称", index = 5)
private String buildingName;
@ExcelProperty(value = "单元名称", index = 6)
private String unitName;
@ExcelProperty(value = "价格", index = 7)
private Integer price;
}
结果:
用于设置行高。
参数 | 含义 |
---|---|
value | 行高,-1 代表自动行高 |
用于设置内容格式。
参数 | 含义 |
---|---|
dataFormat | 日期格式 |
hidden | 设置单元格使用此样式隐藏 |
locked | 设置单元格使用此样式锁定 |
quotePrefix | 在单元格前面增加`符号,数字或公式将以字符串形式展示 |
horizontalAlignment | 设置是否水平居中 |
wrapped | 设置文本是否应换行。将此标志设置为true 通过在多行上显示使单元格中的所有内容可见 |
verticalAlignment | 设置是否垂直居中 |
rotation | 设置单元格中文本旋转角度。03版本的Excel旋转角度区间为-90°90°,07版本的Excel旋转角度区间为0°180° |
indent | 设置单元格中缩进文本的空格数 |
borderLeft | 设置左边框的样式 |
borderRight | 设置右边框样式 |
borderTop | 设置上边框样式 |
borderBottom | 设置下边框样式 |
leftBorderColor | 设置左边框颜色 |
rightBorderColor | 设置右边框颜色 |
topBorderColor | 设置上边框颜色 |
bottomBorderColor | 设置下边框颜色 |
fillPatternType | 设置填充类型 |
fillBackgroundColor | 设置背景色 |
fillForegroundColor | 设置前景色 |
shrinkToFit | 设置自动单元格自动大小 |
用于定制标题字体格式。
参数 | 含义 |
---|---|
fontName | 设置字体名称 |
fontHeightInPoints | 设置字体高度 |
italic | 设置字体是否斜体 |
strikeout | 是否设置删除线 |
color | 设置字体颜色 |
typeOffset | 设置偏移量 |
underline | 设置下划线 |
charset | 设置字体编码 |
bold | 设置字体是否加粗 |
用于设置标题行行高。
参数 | 含义 |
---|---|
value | 设置行高,-1代表自动行高 |
用于设置标题样式。
参数 | 含义 |
---|---|
dataFormat | 日期格式 |
hidden | 设置单元格使用此样式隐藏 |
locked | 设置单元格使用此样式锁定 |
quotePrefix | 在单元格前面增加`符号,数字或公式将以字符串形式展示 |
horizontalAlignment | 设置是否水平居中 |
wrapped | 设置文本是否应换行。将此标志设置为true 通过在多行上显示使单元格中的所有内容可见 |
verticalAlignment | 设置是否垂直居中 |
rotation | 设置单元格中文本旋转角度。03版本的Excel旋转角度区间为-90°90°,07版本的Excel旋转角度区间为0°180° |
indent | 设置单元格中缩进文本的空格数 |
borderLeft | 设置左边框的样式 |
borderRight | 设置右边框样式 |
borderTop | 设置上边框样式 |
borderBottom | 设置下边框样式 |
leftBorderColor | 设置左边框颜色 |
rightBorderColor | 设置右边框颜色 |
topBorderColor | 设置上边框颜色 |
bottomBorderColor | 设置下边框颜色 |
fillPatternType | 设置填充类型 |
fillBackgroundColor | 设置背景色 |
fillForegroundColor | 设置前景色 |
shrinkToFit | 设置自动单元格自动大小 |
不将该字段转换成Excel。
没有添加@ExcelProperty注解的字段都不转换。
用于数字格式化。
参数 | 示例 | 结果 |
---|---|---|
value | @NumberFormat(value = "###.#") |
66.8 |
@NumberFormat(value = "#.##%") | 66.8% | |
@NumberFormat(value = "0.00") | 66.80 |
参数value用#.##和0.00的区别就是,比如导入的数值是66.80,#.##会忽略掉后面的0变成66.8,而0.00则不会忽略,保持原来的66.80,我这里用的是导入时保留2位小数的做法,关于@NumberFormat里面value的参数可以参照java.text.DecimalFormat。
用于时间格式化。
参数 | 示例 | 结果 |
---|---|---|
value | @DateTimeFormat(value="yyyy-MM-dd") | 2023-07-24 |