目录
4、@ContentLoopMerge和@OnceAbsoluteMerge
前言
相信在大部分的web项目中都会有导出导入Excel的需求,今天我们就来看看如何用阿里的EasyExcel组件去实现导出Excel文件的需求。之前我也写过几篇使用Apache POI导出Excel的文章,有兴趣的小伙伴可以去瞅瞅,链接也给大家放出来了:
2、Apache POI导出excel(二):多个sheet
一、EasyExcel简介
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模式,在上层做了模型转换的封装,让使用者更加简单方便。
1、网站
- 官方网站:https://easyexcel.opensource.alibaba.com/
- github地址:https://github.com/alibaba/easyexcel
- gitee地址:https://gitee.com/easyexcel/easyexcel
二、EasyExcel使用
1、相关依赖
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>
2、实现代码
2.1 实体类
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;
}
2.2 服务层
2.2.1 服务接口类
// 导出学生信息
List<StudentVo> exportStudent();
2.2.2 实现类
@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;
}
2.3 控制层
/**
* 导出学生信息
*/
@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:我这里写了两个导出方法。一个是正常导出全部;另一个是指定列导出,只需传入无需导出的列字段集合,导出的时候就会进行过滤导出。
2.4 相关工具类
2.4.1 导出Util
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();
}
}
2.4.2 Excel自定义样式拦截器
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;
}
}
2.4.3 Excel单元格拦截器
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);
}
}
}
2.4.4 Excel自定义列宽拦截器
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的一些样式,小伙伴也可以根据自己的需求,进行优化修改。
2.4.5 性别转换器
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());
}
}
2.4.6 性别枚举
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);
}
}
2.5 结果
导出全部:
导出指定列:
三、EasyExcel常用注解
1、@ExcelProperty
必要的一个字段注解,注解中有三个参数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;
}
2、@ColumnWith
设置列宽度,只有一个参数value,value的单位是字符长度,最大可以设置255个字符,因为一个excel单元格最大可以写入的字符个数就是255个字符。
示例:
public class StudentVo implements Serializable {
/*** 姓名*/
@ExcelProperty(value = "姓名")
@ColumnWidth(10)
private String studentName;
}
3、@ContentFontStyle
用于设置单元格内容字体格式的注解。
参数 | 含义 |
---|---|
fontName | 字体名称 |
fontHeightInPoints | 字体高度 |
italic | 是否斜体 |
strikeout | 是否设置删除水平线 |
color | 字体颜色 |
typeOffset | 偏移量 |
underline | 下划线 |
bold | 是否加粗 |
charset | 编码格式 |
4、@ContentLoopMerge和@OnceAbsoluteMerge
相同点:这两个都是用于设置合并单元格的注解;它们都是按指定行数和指定列数去合并,并不能实现内容相同的合并。
不同点:@ContentLoopMerge是标注在字段上;@OnceAbsoluteMerge是标注在类上。
4.1 @ContentLoopMerge
参数 | 含义 |
---|---|
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;
}
结果:
4.2 @OnceAbsoluteMerge
参数 | 含义 |
---|---|
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;
}
结果:
5、@ContentRowHeight
用于设置行高。
参数 | 含义 |
---|---|
value | 行高,-1 代表自动行高 |
6、@ContentStyle
用于设置内容格式。
参数 | 含义 |
---|---|
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 | 设置自动单元格自动大小 |
7、@HeadFontStyle
用于定制标题字体格式。
参数 | 含义 |
---|---|
fontName | 设置字体名称 |
fontHeightInPoints | 设置字体高度 |
italic | 设置字体是否斜体 |
strikeout | 是否设置删除线 |
color | 设置字体颜色 |
typeOffset | 设置偏移量 |
underline | 设置下划线 |
charset | 设置字体编码 |
bold | 设置字体是否加粗 |
8、@HeadRowHeight
用于设置标题行行高。
参数 | 含义 |
---|---|
value | 设置行高,-1代表自动行高 |
9、@HeadStyle
用于设置标题样式。
参数 | 含义 |
---|---|
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 | 设置自动单元格自动大小 |
10、@ExcelIgnore
不将该字段转换成Excel。
11、@ExcelIgnoreUnannotated
没有添加@ExcelProperty注解的字段都不转换。
12、@NumberFormat
用于数字格式化。
参数 | 示例 | 结果 |
---|---|---|
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。
13、@DateTimeFormat
用于时间格式化。
参数 | 示例 | 结果 |
---|---|---|
value | @DateTimeFormat(value=”yyyy-MM-dd”) | 2023-07-24 |
如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/161424.html