EasyExcel的使用

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

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

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

目录

一、EasyExcelFactory工厂类

二、自定义EasyExcel工具类

三、监听器

 四、实例演示


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

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

一、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<Map < Integer, String>>
     */
    public static List<Map<Integer, String>> syncRead(String filePath) {
        return EasyExcelFactory.read(filePath).sheet().doReadSync();
    }

    /**
     * 同步无模型读取(默认表头占一行,从第2行开始读)
     *
     * @param filePath 文件路径
     * @param sheetNo  sheet页号,从0开始
     * @return List<Map < Integer, String>>
     */
    public static List<Map<Integer, String>> 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<Map < colNum, cellValue>>
     */
    public static List<Map<Integer, String>> 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<Map < colNum, cellValue>>
     */
    public static List<Map<Integer, String>> 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<Map < colNum, cellValue>>
     */
    public static List<Map<Integer, String>> 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<T>
     */
    public static List<T> 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<T>
     */
    public static List<T> 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<T>
     */
    public static List<T> 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<T>
     */
    public static List<T> 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<T>
     */
    public static List<T> 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<T> 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<T> excelListener

这里需要我们,需要传一个AnalysisEventListener<T>进去,我们可以根据进行自己需求的扩展AnalysisEventListener这个类,AnalysisEventListener又继承了ReadListener,我们看一下ReadListener。其中有几个方法。几个方法的用处我把自己的理解敲了上去。

public interface ReadListener<T> extends Listener {

    // 在转换异常获取其他异常下会调用本接口。
    void onException(Exception var1, AnalysisContext var2) throws Exception;

    //读取表头数据存在headMap中
    void invokeHead(Map<Integer, CellData> 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 <T> 泛型
 * @author Lyle
 */
public class ExcelListener<T> extends AnalysisEventListener<T> {
    // 保存读取的对象
    private final List<T> rows = new ArrayList<>();
    // 日志输出
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private String sheetName = "";
    // 获取对应类
    private Class headClazz;
    // 此map用来存储错误信息
    private final List<String> errorMessage = new ArrayList<>();

    public ExcelListener(Class headClazz) {
        this.headClazz = headClazz;
    }

    /**
     * @param headClazz
     * @Description 通过class获取类字段信息
     */
    public Map<Integer, String> getIndexNameMap(Class headClazz) throws NoSuchFieldException {
        Map<Integer, String> 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<Integer, String> headMap, AnalysisContext context) {
        logger.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
        Map<Integer, String> head = new HashMap<>();
        try {
            head = getIndexNameMap(headClazz);   //通过class获取到使用@ExcelProperty注解配置的字段
            logger.info(String.valueOf(head));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        Set<Integer> 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<T> getRows() {
        return rows;
    }

    public Class getHeadClazz() {
        return headClazz;
    }

    public List<String> 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<String> 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可以让我们的开发效率提升数倍,大家有什么不理解的可以私信,或者在下方评论里留言。我也是小白,更深入的我还是不够了解。大家可以一起交流!

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
EasyExcel是阿里巴巴开源的一个excel处理框架,使用简单且节省内存。它适合处理大数据量的Excel,不像之前的Excel解析框架将数据一次性加载到内存中,而是从磁盘上逐行读取数据并解析。它重写了poi对07版本Excel的解析,在处理大数据时不容易发生内存溢出。EasyExcel可以用于数据导入、数据导出以及数据传输等场景。在使用EasyExcel时,可以使用注解来自定义表头、选择excel中的顺序、忽略不需要导出的属性、进行日期格式转换,以及设置行宽等。官方文档提供了更详细的使用说明。 在实际应用中,可以使用@ExcelIgnore注解来标记不需要导出的多余属性,这样可以避免导出不需要的字段。另外,可以使用@DateTimeFormat注解来进行日期格式转换,使用@ColumnWidth注解来设置行宽。 总的来说,EasyExcel是一个方便易用的excel处理框架,可以帮助我们简化Excel的处理操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [EasyExcel使用教程](https://blog.csdn.net/tttalk/article/details/123379580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员Lyle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值