> 文章列表 > 【JAVA】easyExcel导出导入使用

【JAVA】easyExcel导出导入使用

【JAVA】easyExcel导出导入使用

EasyExcel是阿里巴巴开源插件之一,主要解决了poi框架使用复杂,sax解析模式不容易操作,数据量大起来容易OOM,解决了POI并发造成的报错。主要解决方式:通过解压文件的方式加载,一行一行地加载,并且抛弃样式字体等不重要的数据,降低内存的占用。

EasyExcel在SpringBoot的集成 引入对应的pom依赖

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.11</version>
</dependency>

excel常用注解

@ExcelProperty
@ColumnWith 列宽
@ContentFontStyle 文本字体样式
@ContentLoopMerge 文本合并
@ContentRowHeight 文本行高度
@ContentStyle 文本样式
@HeadFontStyle 标题字体样式
@HeadRowHeight 标题高度
@HeadStyle 标题样式
@ExcelIgnore 忽略项
@ExcelIgnoreUnannotated 忽略未注解

@Data
public class Member {/*** EasyExcel使用:导出时忽略该字段*/@ExcelIgnoreprivate Integer id;@ExcelProperty("用户名")@ColumnWidth(20)private String username;/*** EasyExcel使用:日期的格式化*/@ColumnWidth(20)@ExcelProperty("出生日期")@DateTimeFormat("yyyy-MM-dd")private Date birthday;/*** EasyExcel使用:自定义转换器*/@ColumnWidth(10)@ExcelProperty(value = "性别", converter = GenderConverter.class)private Integer gender;
}

GenderConverter转换器的代码实现如下:


public class GenderConverter implements Converter<Integer> {private static final String MAN = "男";private static final String WOMAN = "女";
​
​@Overridepublic Class<?> supportJavaTypeKey() {// 实体类中对象属性类型return Integer.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {// Excel中对应的CellData属性类型return CellDataTypeEnum.STRING;}@Overridepublic Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty,GlobalConfiguration globalConfiguration) {// 从Cell中读取数据String gender = cellData.getStringValue();// 判断Excel中的值,将其转换为预期的数值if (MAN.equals(gender)) {return 0;} else if (WOMAN.equals(gender)) {return 1;}return null;}@Overridepublic CellData<?> convertToExcelData(Integer integer, ExcelContentProperty excelContentProperty,GlobalConfiguration globalConfiguration) {// 判断实体类中获取的值,转换为Excel预期的值,并封装为CellData对象if (integer == null) {return new CellData<>("");} else if (integer == 0) {return new CellData<>(MAN);} else if (integer == 1) {return new CellData<>(WOMAN);}return new CellData<>("");}
}

字段注解 类注解
@ColumnWith(列宽)
@ColumnWidth(全局列宽)
@ExcelProperty(字段配置)
@HeadFontStyle(头样式)
@HeadRowHeight(标题高度)
@ContentFontStyle(内容字体样式)
@ContentRowHeight(内容高度)
@ExcelProperty
必要的一个注解,注解中有三个参数value,index,converter分别代表列明,列序号,数据转换方式 value和index只能二选一,通常不用设置converter
1.value 通过标题文本对应
2.index 通过文本行号对应
3.converter 转换器,通常入库和出库转换使用,如性别入库0和1,出库男和女

public class ImeiEncrypt {@ExcelProperty(value = "值")private String valueField;@ExcelProperty(value = 1,converter =IndustryIdConverter.class)private String indexField;@ExcelProperty(value = "值对应和转换器",converter =IndustryIdConverter.class)private String valueAndConverterField;
}

@ColumnWith 设置列宽度,只有一个参数value,value的单位是字符长度,最大可以设置255个字符,因为一个excel单元格最大可以写入的字符个数就是255个字符。

public class ImeiEncrypt {@ColumnWidth(value = 18)private String imei;
}

@ContentFontStyle 用于设置单元格内容字体格式的注解

参数:
fontName 字体名称
fontHeightInPoints 字体高度
italic 是否斜体
strikeout 是否设置删除水平线
color 字体颜色
typeOffset 偏移量
underline 下划线
bold 是否加粗
charset 编码格式

@ContentLoopMerge 用于设置合并单元格的注解

参数:
eachRow
columnExtend

@ContentRowHeight 用于设置行高

参数:
value 行高,-1代表自动行高

@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 设置自动单元格自动大小

@HeadFontStyle 用于定制标题字体格式

参数 含义
fontName 设置字体名称
fontHeightInPoints 设置字体高度
italic 设置字体是否斜体
strikeout 是否设置删除线
color 设置字体颜色
typeOffset 设置偏移量
underline 设置下划线
charset 设置字体编码
bold 设置字体是否加粗

@HeadRowHeight 设置标题行行高

参数 含义
value 设置行高,-1代表自动行高

@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 设置自动单元格自动大小

@ExcelIgnore 不将该字段转换成Excel

@ExcelIgnoreUnannotated 没有注解的字段都不转换

基础综合示例

//行高全部设为40
@HeadRowHeight(value = 40)
//标题全部居中
@HeadStyle(horizontalAlignment = CENTER)
public class SupervisionDailyExportProcessDTO {//"二、问题整改情况"是大标题,"序号"是大标题下面的子标题@ExcelProperty({"二、问题整改情况", "序号"})@ContentStyle(horizontalAlignment = CENTER)private Integer id;@ExcelProperty({"二、问题整改情况", "问题来源"})//单独设置这一列列宽为40@ColumnWidth(value = 40)@ContentStyle(wrapped = true, horizontalAlignment = CENTER)private String questionOrigin;@ExcelProperty({"二、问题整改情况", "督察点位"})@ColumnWidth(value = 40)@ContentStyle(wrapped = true, horizontalAlignment = CENTER)private String supervisionPoint;@ExcelProperty({"二、问题整改情况", "问题内容"})@ColumnWidth(value = 40)@ContentStyle(wrapped = true, horizontalAlignment = CENTER)private String questionContents;@ExcelProperty({"二、问题整改情况", "责任单位"})@ColumnWidth(value = 30)@ContentStyle(horizontalAlignment = CENTER)private String orgName;@ExcelProperty({"二、问题整改情况", "整改情况"})@ColumnWidth(value = 40)@ContentStyle(wrapped = true, horizontalAlignment = CENTER)private String processSituation;
}

补充颜色

NPOI Excel 单元格颜色对照表,在引用了 NPOI.dll 后可通过 ICellStyle 接口的 FillForegroundColor 属性实现 Excel 单元格的背景色设置,FillPattern 为单元格背景色的填充样式。
NPOI Excel 单元格背景颜色设置方法以及颜色对照表:

ICellStyle style = workbook.CreateCellStyle();
style.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Red.Index;
style.FillPattern = FillPattern.SolidForeground;ICell cell = workbook.CreateSheet().CreateRow(0).CreateCell(0);
cell.CellStyle = style;

【JAVA】easyExcel导出导入使用
【JAVA】easyExcel导出导入使用

easyExcel使用

EasyExcelUtils 导出工具类

package com.sjasoft.framework.utils;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.springframework.lang.Nullable;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;import javax.print.DocFlavor;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;/*** 导出工具类** @author zlf*/
public class EasyExcelUtils {/*** 导出公共方法** @param datas    要导出的数据集* @param clazz    导出的实体* @param fileName 导出文件名称* @throws IOException*/public static void export(List datas, @Nullable Class<?> clazz, String fileName, String sheetName) throws IOException {ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletResponse response = sra.getResponse();response.setContentType("application/vnd.ms-excel;charset=utf-8");// 设置文件名String exportFileName = URLEncoder.encode(fileName, "UTF-8");//String exportSheetName = URLEncoder.encode(sheetName, "UTF-8");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + exportFileName + ExcelTypeEnum.XLSX.getValue());// 创建一个写出的单元格样式对象WriteCellStyle headWriteCellStyle = new WriteCellStyle();// 设置水平对齐方式headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);// 设置填充前景色headWriteCellStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());// 创建写出Excel的字体对象WriteFont headWriteFont = new WriteFont();//设置字体高度//headWriteFont.setFontHeightInPoints((short) 10);headWriteCellStyle.setWriteFont(headWriteFont);headWriteCellStyle.setWrapped(true);// 创建一个写出的单元格样式对象WriteCellStyle contentWriteCellStyle = new WriteCellStyle();//设置内容靠中对齐//contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);// 边框设置/*contentWriteCellStyle.setBorderTop(BorderStyle.THIN);             // 设置单元格上边框为细线contentWriteCellStyle.setBorderBottom(BorderStyle.THICK);         // 设置单元格下边框为粗线contentWriteCellStyle.setBorderLeft(BorderStyle.MEDIUM);         // 设置单元格左边框为中线contentWriteCellStyle.setBorderRight(BorderStyle.MEDIUM_DASHED); // 设置单元格右边框为中虚线*/// 设置行样式HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);// 设置自定义自适应列宽样式ExcelWidthStyleStrategy excelWidthStyleStrategy = new ExcelWidthStyleStrategy();// 如果不用模板的方式导出的话,是doWriteEasyExcel.write(response.getOutputStream(), clazz).excelType(ExcelTypeEnum.XLSX).autoCloseStream(true).registerWriteHandler(horizontalCellStyleStrategy).registerWriteHandler(excelWidthStyleStrategy).sheet(sheetName).doWrite(datas);}/*** 根据Excel模板,批量导入数据** @param file  导入的Excel* @param clazz 解析的类型* @return 解析完成的数据*/public static List<?> importExcel(MultipartFile file, Class<?> clazz) {if (file == null || file.isEmpty()) {throw new RuntimeException("没有文件或者文件内容为空!");}List<?> dataList;BufferedInputStream ipt;try {InputStream is = file.getInputStream();// 用缓冲流对数据流进行包装ipt = new BufferedInputStream(is);// 数据解析监听器ExcelListener<?> listener = new ExcelListener<>();// 读取数据EasyExcel.read(ipt, clazz, listener).sheet().doRead();// 获取去读完成之后的数据dataList = listener.getList();} catch (Exception e) {throw new RuntimeException("数据导入失败!" + e);}return dataList;}
}

ExcelListener Excel数据解析监听器, 数据解析方法异步执行

package com.sjasoft.framework.utils;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** Excel数据解析监听器, 数据解析方法异步执行** @param <T> Excel中数据的类型* @author zlf*/
@Getter
@Setter
@NoArgsConstructor
public class ExcelListener<T> extends AnalysisEventListener<T> {// 加入一个判断标签,判断数据是否已经读取完private volatile boolean retryLock = false;// 解析完成后的数据集合, 监听对象初始化之后,立即初始化集合对象private final List<T> dataList = new ArrayList<>();// 每次最多导入条数private final int batchSize = 2000;/*** 获取解析后的数据集合, 如果数据还没有被解析完成,会对读取该集合的线程进行阻塞,直到数据读取完成之后,进行解锁。* 如果一次导入数据超过batchSize条,则以抛异常的形式阻止导入数据** @return 解析后的数据集合*/public List<T> getList() {while (true) {if (retryLock) {if (dataList.size() > batchSize) {// 手动清空数据内存数据,减少内存消耗dataList.clear();throw new RuntimeException("一次最多导入" + batchSize + "条数据");} else {return dataList;}}}}/*** Excel每解析一行数据,就会调用一次该方法** @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}* @param context analysis context*/@Overridepublic void invoke(T data, AnalysisContext context) {dataList.add(data);}/*** 读取表头内容** @param headMap 表头部数据* @param context 数据解析上下文*/@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {// 在此处可以进行表头的校验,headMap对应的就是表头的数据// 在此处有个很奇怪的地方,就是当用户导入的文件没有任何表头的时候,也就是表头这一行为空// 表头检验则不会进行这个函数,且数据导入成功,所以这个地方要确认清楚// 最后我是在拿到所有数据,包括表头,再取第一行为表头进行校验//System.out.println("表头:" + headMap);}/*** 流中的数据解析完成之后,就会调用此方法*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 数据解析完成,解锁retryLock = true;}/*** 解析过程如果发生异常,会调用此方法*/@Overridepublic void onException(Exception exception, AnalysisContext context) {throw new RuntimeException("Excel数据异常,请检查或联系管理员!");}
}

ExcelWidthStyleStrategy 自适应宽度工具类

package com.sjasoft.framework.utils;
import com.alibaba.excel.enums.CellDataTypeEnum;
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.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Cell;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class ExcelWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {// 单元格的最大宽度private static final int MAX_COLUMN_WIDTH = 200;// 缓存(第一个Map的键是sheet的index, 第二个Map的键是列的index, 值是数据长度)private final Map<Integer, Map<Integer, Integer>> CACHE = new HashMap<>(8);// 重写设置列宽的方法@Overrideprotected void setColumnWidth(WriteSheetHolder writeSheetHolder,List<WriteCellData<?>> cellDataList,Cell cell,Head head,Integer relativeRowIndex,Boolean isHead) {boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);// 当时表头或者单元格数据列表有数据时才进行处理if (needSetWidth) {Map<Integer, Integer> maxColumnWidthMap = CACHE.computeIfAbsent(writeSheetHolder.getSheetNo(), k -> new HashMap<>(16));// 获取数据长度Integer columnWidth = this.dataLength(cellDataList, cell, isHead);if (columnWidth >= 0) {if (columnWidth > MAX_COLUMN_WIDTH) {columnWidth = MAX_COLUMN_WIDTH;}// 确保一个列的列宽以表头为主,如果表头已经设置了列宽,单元格将会跟随表头的列宽Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());if (maxColumnWidth == null || columnWidth > maxColumnWidth) {maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);// 如果使用EasyExcel默认表头,那么使用columnWidth * 512// 如果不使用EasyExcel默认表头,那么使用columnWidth * 256// 如果是自己定义的字体大小,可以再去测试这个参数常量writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 512);}}}}/*** 获取当前单元格的数据长度*/private Integer dataLength(List<WriteCellData<?>> cellDataList,Cell cell,Boolean isHead) {if (isHead) {return cell.getStringCellValue().getBytes().length;} else {WriteCellData cellData = cellDataList.get(0);CellDataTypeEnum type = cellData.getType();if (type == null) {return -1;} else {switch (type) {case STRING:return cellData.getStringValue().getBytes().length;case BOOLEAN:return cellData.getBooleanValue().toString().getBytes().length;case NUMBER:return cellData.getNumberValue().toString().getBytes().length;default:return -1;}}}}
}

调用

Controller

   @ApiOperation("导出日志明细")@PostMapping("/exportLogDetailInfo")public void exportLogDetailInfo(@RequestBody LogDetailParam detailParam) throws IOException{logService.exportLogDetailInfo(detailParam);}

service

  @SystemLog("导出日志明细")public void exportLogDetailInfo(LogDetailParam detailParam) throws IOException {List<LogDetailVo> logDetailList = tLogListDao.getLogDetailInfo(detailParam);if(CollectionUtil.isEmpty(logDetailList)){throw  new RuntimeException("未查询到数据,请调整条件后重新导出");}//导出文件名称String fileName = new String("导出日志明细" + DateUtil.format(new Date(),"yyyyMMddHHmmss"));EasyExcelUtils.export(logDetailList,LogDetailVo.class,fileName,"导出日志明细");}