赞
踩
目录
4、@ContentLoopMerge和@OnceAbsoluteMerge
相信在大部分的web项目中都会有导出导入Excel的需求,今天我们就来看看如何用阿里的EasyExcel组件去实现导出Excel文件的需求。之前我也写过几篇使用Apache POI导出Excel的文章,有兴趣的小伙伴可以去瞅瞅,链接也给大家放出来了:
2、Apache POI导出excel(二):多个sheet
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
- <!-- easyExcel -->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>easyexcel</artifactId>
- <version>3.2.1</version>
- <!-- 3+以上版本的easyExcel,使用poi 5+以上版本时,需要手动排除:poi-ooxml-schemas -->
- <exclusions>
- <exclusion>
- <artifactId>poi-ooxml-schemas</artifactId>
- <groupId>org.apache.poi</groupId>
- </exclusion>
- </exclusions>
- </dependency>
-
- 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<StudentVo> exportStudent();
- @Override
- public List<StudentVo> exportStudent() {
-
- List<StudentVo> 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<StudentVo> list = userService.exportStudent();
-
- // 指定列导出
- String column = "studentName,age,phone";// 定义无需导出的列字段
- if (StringUtils.isNotEmpty(column)) {
- List<String> 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<String> excludeColumns) {
- baseExportExcel(response, clazz, fileName, list, excludeColumns);
- }
-
- public static void baseExportExcel(HttpServletResponse response, Class<?> clazz, String fileName, List<?> list, List<String> 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<WriteCellData<?>> 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<Integer> {
-
- @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<Integer> 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 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。