EasyExcel的使用

her~~llo,我是你们的好朋友Lyle,是名梦想成为计算机大佬的男人!

博客是为了记录自我的学习历程,加强记忆方便复习,如有不足之处还望多多包涵!非常欢迎大家的批评指正。

最近开发项目需要用到比较复杂的高级导入,于是就学习了EasyExcel的使用,现在总结一下。有什么新内容会持续更新。

目录

一、EasyExcelFactory工厂类

二、自定义EasyExcel工具类

三、监听器

 四、实例演示


首先说一下版本吧,我使用的是2.2.7的版本。

        
            com.alibaba
            easyexcel
            2.2.7
        

一、EasyExcelFactory工厂类

首先我建议大家可以看一下EasyExcel为我们提供的工厂类EasyExcelFactory代码,其中经常用到的有这些:

读取文件导入的话就是

read(file).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();

写文件导出的话就是

write(filePath).head(head).sheet(sheetNo, sheetName).doWrite(data);

看懂源代码的话,对我们自己编写适合自己项目的方法很用用处。

二、自定义EasyExcel工具类

实际开发中,我们在导入excel文件时有很多种情况,我在下方各种情况都列举出来了,其中展示了同步读取的情况的对应代码,因为太多了,不方便展示。有需要学习的小伙伴可以私信我。

/**
 * EasyExcel工具类
 */
public class EasyExcelUtils {
    /**
     * 同步无模型读取(默认读取sheet0,从第2行开始读)
     *
     * @param filePath 文件路径
     * @return List>
     */
    public static List> syncRead(String filePath) {
        return EasyExcelFactory.read(filePath).sheet().doReadSync();
    }

    /**
     * 同步无模型读取(默认表头占一行,从第2行开始读)
     *
     * @param filePath 文件路径
     * @param sheetNo  sheet页号,从0开始
     * @return List>
     */
    public static List> syncRead(String filePath, Integer sheetNo) {
        return EasyExcelFactory.read(filePath).sheet(sheetNo).doReadSync();
    }

    /**
     * 同步无模型读取(指定sheet和表头占的行数)
     *
     * @param inputStream 输入流
     * @param sheetNo     sheet页号,从0开始
     * @param headRowNum  表头占的行数,从0开始(如果要连表头一起读出来则传0)
     * @return List>
     */
    public static List> syncRead(InputStream inputStream, Integer sheetNo, Integer headRowNum) {
        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> syncRead(File file, Integer sheetNo, Integer headRowNum) {
        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> syncRead(String filePath, Integer sheetNo, Integer headRowNum) {
        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和表头占的行数)
     */

}

三、监听器

监听器在我看来就是为了对读取的数据进行校验(空校验,类型校验),用于异步读取,我在上面展示了异步按模型读取文件的对应代码,其中有一个参数就是

AnalysisEventListener excelListener

这里需要我们,需要传一个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());
        }
    }
}

 运行结果图:

EasyExcel的使用_第1张图片

 结语:

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

你可能感兴趣的:(EasyExcel,java)