her~~llo,我是你们的好朋友Lyle,是名梦想成为计算机大佬的男人!
博客是为了记录自我的学习历程,加强记忆方便复习,如有不足之处还望多多包涵!非常欢迎大家的批评指正。
最近开发项目需要用到比较复杂的高级导入,于是就学习了EasyExcel的使用,现在总结一下。有什么新内容会持续更新。
目录
首先说一下版本吧,我使用的是2.2.7的版本。
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.7</version> </dependency>
首先我建议大家可以看一下EasyExcel为我们提供的工厂类EasyExcelFactory代码,其中经常用到的有这些:
读取文件导入的话就是
read(file).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();写文件导出的话就是
write(filePath).head(head).sheet(sheetNo, sheetName).doWrite(data);看懂源代码的话,对我们自己编写适合自己项目的方法很用用处。
实际开发中,我们在导入excel文件时有很多种情况,我在下方各种情况都列举出来了,其中展示了同步读取的情况的对应代码,因为太多了,不方便展示。有需要学习的小伙伴可以私信我。
- /**
- * EasyExcel工具类
- */
- public class EasyExcelUtils {
- /**
- * 同步无模型读取(默认读取sheet0,从第2行开始读)
- *
- * @param filePath 文件路径
- * @return List
- */
- public static List
- return EasyExcelFactory.read(filePath).sheet().doReadSync();
- }
-
- /**
- * 同步无模型读取(默认表头占一行,从第2行开始读)
- *
- * @param filePath 文件路径
- * @param sheetNo sheet页号,从0开始
- * @return List
- */
- public static List
- return EasyExcelFactory.read(filePath).sheet(sheetNo).doReadSync();
- }
-
- /**
- * 同步无模型读取(指定sheet和表头占的行数)
- *
- * @param inputStream 输入流
- * @param sheetNo sheet页号,从0开始
- * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
- * @return List
- */
- public static List
- return EasyExcelFactory.read(inputStream).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
- }
-
- /**
- * 同步无模型读取(指定sheet和表头占的行数)
- *
- * @param file 文件
- * @param sheetNo sheet页号,从0开始
- * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
- * @return List
- */
- public static List
- return EasyExcelFactory.read(file).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
- }
-
- /**
- * 同步无模型读取(指定sheet和表头占的行数)
- *
- * @param filePath 文件路径
- * @param sheetNo sheet页号,从0开始
- * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
- * @return List
- */
- public static List
- return EasyExcelFactory.read(filePath).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
- }
-
- /**
- * 同步按模型读取(默认读取sheet0,从第2行开始读)
- *
- * @param filePath 文件路径
- * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
- * @return List
- */
- public static List
syncReadModel(String filePath, Class> clazz) { - return EasyExcelFactory.read(filePath).sheet().head(clazz).doReadSync();
- }
-
- /**
- * 同步按模型读取(默认表头占一行,从第2行开始读)
- *
- * @param filePath 文件路径
- * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
- * @param sheetNo sheet页号,从0开始
- * @return List
- */
- public static List
syncReadModel(String filePath, Class> clazz, Integer sheetNo) { - return EasyExcelFactory.read(filePath).sheet(sheetNo).head(clazz).doReadSync();
- }
-
- /**
- * 同步按模型读取(指定sheet和表头占的行数)
- *
- * @param inputStream 输入流
- * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
- * @param sheetNo sheet页号,从0开始
- * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
- * @return List
- */
- public static List
syncReadModel(InputStream inputStream, Class> clazz, Integer sheetNo, Integer headRowNum) { - return EasyExcelFactory.read(inputStream).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
- }
-
- /**
- * 同步按模型读取(指定sheet和表头占的行数)
- *
- * @param file 文件
- * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
- * @param sheetNo sheet页号,从0开始
- * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
- * @return List
- */
- public static List
syncReadModel(File file, Class> clazz, Integer sheetNo, Integer headRowNum) { - return EasyExcelFactory.read(file).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
- }
-
- /**
- * 同步按模型读取(指定sheet和表头占的行数)
- *
- * @param filePath 文件路径
- * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
- * @param sheetNo sheet页号,从0开始
- * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
- * @return List
- */
- public static List
syncReadModel(String filePath, Class> clazz, Integer sheetNo, Integer headRowNum) { - return EasyExcelFactory.read(filePath).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
- }
-
-
- /**
- * 异步按模型读取(默认表头占一行,从第2行开始读)
- */
-
- /**
- * 异步按模型读取
- *
- * @param file 文件
- * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
- * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
- * @param sheetNo sheet页号,从0开始
- * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
- */
- public static void asyncReadModel(File file, AnalysisEventListener
excelListener, Class> clazz, Integer sheetNo, Integer headRowNum) { - EasyExcelFactory.read(file, clazz, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
- }
-
- /**
- * 异步按模型读取 文件路径
- */
-
- /**
- * 异步按模型读取 输入流
- *
- */
-
- /**
- * 无模板写文件 文件路径
- */
-
-
- /**
- * 无模板写文件 指定sheet名称
- */
-
- /**
- * 根据excel模板文件写入文件
- */
-
- /**
- * 根据excel模板文件写入文件
- *
- */
-
- /**
- * 按模板写文件
- */
-
- /**
- * 按模板写文件
- *
- */
-
- /**
- * 按模板写文件(包含某些字段)
- */
-
- /**
- * 按模板写文件(排除某些字段)
- */
-
- /**
- * 多个sheet页的数据链式写入
- */
-
-
- /**
- * 多个sheet页的数据链式写入(失败了会返回一个有部分数据的Excel)
- */
-
- /**
- * 同步按模型读,设置监听(指定sheet和表头占的行数)
- */
-
- }
监听器在我看来就是为了对读取的数据进行校验(空校验,类型校验),用于异步读取,我在上面展示了异步按模型读取文件的对应代码,其中有一个参数就是
AnalysisEventListenerexcelListener 这里需要我们,需要传一个AnalysisEventListener
进去,我们可以根据进行自己需求的扩展AnalysisEventListener这个类,AnalysisEventListener又继承了ReadListener,我们看一下ReadListener。其中有几个方法。几个方法的用处我把自己的理解敲了上去。
- public interface ReadListener
extends Listener { -
- // 在转换异常获取其他异常下会调用本接口。
- void onException(Exception var1, AnalysisContext var2) throws Exception;
-
- //读取表头数据存在headMap中
- void invokeHead(Map
var1, AnalysisContext var2) ; -
- //读取一行一行数据到var1
- void invoke(T var1, AnalysisContext var2);
-
- void extra(CellExtra var1, AnalysisContext var2);
-
- //AOP思想,在完成数据解析后进行的操作
- void doAfterAllAnalysed(AnalysisContext var1);
-
- boolean hasNext(AnalysisContext var1);
- }
在这里我提供一个自己写的代码,加深大家的理解。
- /**
- * 修改默认监听器,增加特殊需求
- *
- * @param
泛型 - * @author Lyle
- */
- public class ExcelListener
extends AnalysisEventListener { - // 保存读取的对象
- private final List
rows = new ArrayList<>(); - // 日志输出
- private final Logger logger = LoggerFactory.getLogger(getClass());
- private String sheetName = "";
- // 获取对应类
- private Class headClazz;
- // 此map用来存储错误信息
- private final List
errorMessage = new ArrayList<>(); -
- public ExcelListener(Class headClazz) {
- this.headClazz = headClazz;
- }
-
- /**
- * @param headClazz
- * @Description 通过class获取类字段信息
- */
- public Map
getIndexNameMap(Class headClazz) throws NoSuchFieldException { - Map
result = new HashMap<>(); - Field field;
- Field[] fields = headClazz.getDeclaredFields(); //获取类中所有的属性
- for (int i = 0; i < fields.length; i++) {
- field = headClazz.getDeclaredField(fields[i].getName());
- // logger.info(String.valueOf(field));
- field.setAccessible(true);
- ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);//获取根据注解的方式获取ExcelProperty修饰的字段
- if (excelProperty != null) {
- int index = excelProperty.index(); //索引值
- String[] values = excelProperty.value(); //字段值
- StringBuilder value = new StringBuilder();
- for (String v : values) {
- value.append(v);
- }
- result.put(index, value.toString());
- }
- }
- return result;
- }
-
- @Override
- public void invokeHeadMap(Map
headMap, AnalysisContext context) { - logger.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
- Map
head = new HashMap<>(); - try {
- head = getIndexNameMap(headClazz); //通过class获取到使用@ExcelProperty注解配置的字段
- logger.info(String.valueOf(head));
- } catch (NoSuchFieldException e) {
- e.printStackTrace();
- }
- Set
keySet = head.keySet(); //解析到的excel表头和实体配置的进行比对 - for (Integer key : keySet) {
- if (StringUtils.isEmpty(headMap.get(key))) {
- errorMessage.add("您上传的文件第" + (key + 1) + "列表头为空,请安照模板检查后重新上传");
- }
- if (!headMap.get(key).equals(head.get(key))) {
- errorMessage.add("您上传的文件第" + (key + 1) + "列表头与模板表头不一致,请检查后重新上传");
- }
- }
- }
-
- @Override
- public void invoke(T object, AnalysisContext context) {
- // 实际数据量比较大时,rows里的数据可以存到一定量之后进行批量处理(比如存到数据库),
- // 然后清空列表,以防止内存占用过多造成OOM
- rows.add(object);
- }
-
- @Override
- public void doAfterAllAnalysed(AnalysisContext context) {
- // 当前sheet的名称 编码获取类似
- sheetName = context.readSheetHolder().getSheetName();
- logger.info(sheetName);
- logger.info("read {} rows", rows.size());
- }
-
- /**
- * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
- *
- * @param exception 抛出异常
- * @param context 解析内容
- */
- @Override
- public void onException(Exception exception, AnalysisContext context) {
- logger.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
- if (exception instanceof ExcelDataConvertException) {
- ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
- errorMessage.add("第" + excelDataConvertException.getRowIndex() + "行,第" + (excelDataConvertException.getColumnIndex() + 1) + "列数据类型解析异常,数据为:" + excelDataConvertException.getCellData());
- logger.error("第{}行,第{}列数据类型解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
- excelDataConvertException.getColumnIndex() + 1, excelDataConvertException.getCellData());
- }
- }
-
- public List
getRows() { - return rows;
- }
-
- public Class getHeadClazz() {
- return headClazz;
- }
-
- public List
getErrorMessage() { - return errorMessage;
- }
-
- public String getSheetName() {
- return sheetName;
- }
- }
首先我们需要一个模板类TestDemo,也就是我们在数据库存取的实体类。
- package com.swpu.component.commons.utils;
-
- import com.alibaba.excel.annotation.ExcelProperty;
-
- public class TestDemo {
- @ExcelProperty(value = "地层压力",index = 0)
- private float diCengYali;
- @ExcelProperty(value = "破裂压力",index = 1)
- private float poLieYaLi;
- @ExcelProperty(value = "井径扩大率",index = 2)
- private float jingJingKuoDa;
-
- @Override
- public String toString() {
- return "TestDemo{" +
- "diCengYali=" + diCengYali +
- ", poLieYaLi=" + poLieYaLi +
- ", jingJingKuoDa=" + jingJingKuoDa +
- '}';
- }
-
- public float getDiCengYali() {
- return diCengYali;
- }
-
- public void setDiCengYali(float diCengYali) {
- this.diCengYali = diCengYali;
- }
-
- public float getPoLieYaLi() {
- return poLieYaLi;
- }
-
- public void setPoLieYaLi(float poLieYaLi) {
- this.poLieYaLi = poLieYaLi;
- }
-
- public float getJingJingKuoDa() {
- return jingJingKuoDa;
- }
-
- public void setJingJingKuoDa(float jingJingKuoDa) {
- this.jingJingKuoDa = jingJingKuoDa;
- }
- }
测试运行代码:
- public class ExcelUtilsTest {
- @Test
- @DisplayName("测试Excel导入")
- void testExcel() throws IOException {
-
- //待解析的文件路径
-
- File file = new File("C:\\Users\\Administrator\\Desktop\\测试.xlsx");
- //声明监听器
-
- ExcelListener myListener=new ExcelListener(TestDemo.class);
- //调用EasyExcelUtils
-
- EasyExcelUtils.asyncReadModel(file, myListener,TestDemo.class,0, 1);
-
- //获取解决出的错误信息
- List
errorMessage = myListener.getErrorMessage(); - System.out.println(errorMessage);
-
- //获取监听器读到的数据,拿到的数据大家可以根据需求进行数据库操作
- List rows = myListener.getRows();
- for (Object testDemo:rows){
- System.out.println(testDemo.toString());
- }
- }
- }
运行结果图:

结语:
对于一键导入Excel这一功能,我刚开始感觉确实很难实现,EasyExcel可以帮我们解决了很多复杂的if判断,理解EasyExcel的实现,灵活使用EasyExcel可以让我们的开发效率提升数倍,大家有什么不理解的可以私信,或者在下方评论里留言。我也是小白,更深入的我还是不够了解。大家可以一起交流!