EasyExcel的使用--读取

时间:2025-02-07 13:21:43

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。

今天根据官网(/easyexcel/doc/read)指导,试了一个简单的读取例子,如下。

了解常见API的话,访问地址:常见api · 语雀 ()

文件中引入:

<dependency>
    <groupId></groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.10</version>
</dependency>

2.创建导入实体:

@Data
public class ImportContent {
    @ExcelProperty("编码")
    private String id;
    @ExcelProperty("名称")
    private String test;
}

 如果想要定义格式,如下:

@Data
public class ConverterData {
    /**
     * 自定义转换器
     */
    @ExcelProperty(converter = )
    private String string;
    /**
     * easyexcel时间格式定义
     */
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    private String date;
    /**
     * easyexcel数字格式定义
     */
    @NumberFormat("#.##%")
    private String doubleData;
}

如需解析公式,实体定义如下:

/**
 * 带有公式的实体
 */
@Data
public class CellDataReadDemoData {
    private CellData<String> string;
    // 这里注意 虽然是日期 但是 类型 存储的是number 因为excel 存储的就是number
    private CellData<Date> date;
    private CellData<Double> doubleData;
    // 这里并不一定能完美的获取 有些公式是依赖性的 可能会读不到 这个问题后续会修复
    private CellData<String> formulaValue;
}

3.创建监听器:

/**
 * 导入demo监听器
 */
public class DemoTestListener extends AnalysisEventListener<ImportContent> {
    private static final Logger LOGGER = ();
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<ImportContent> list = new ArrayList<ImportContent>();

    /**
     * 读取时,每条数据都会从这里解析
     */
    @Override
    public void invoke(ImportContent data, AnalysisContext context) {
        Gson gson = new Gson();
        ("解析到一条数据:{}", (data));
        (data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            ();
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        ("所有数据解析完成!");
    }
    /**
     * 加上存储数据库
     */
    private void saveData() {
        ("{}条数据,开始存储数据库!", ());
        //这里真实的保存数据。
        ("存储数据库成功!");
    }

    /**
     * 读取表头数据
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        Gson gson = new Gson();
        ("解析到一条头数据:{}", (headMap));
    }

    /**
     * 读取额外信息
     * @param extra
     * @param context
     */
    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        Gson gson = new Gson();
        ("读取到了一条额外信息:{}", (extra));
        switch (()) {
            case COMMENT:
                ("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", (), (),
                        ());
                break;
            case HYPERLINK:
                if ("Sheet1!A1".equals(())) {
                    ("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}", (),
                            (), ());
                } else if ("Sheet2!A1".equals(())) {
                    (
                            "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{},"
                                    + "内容是:{}",
                            (), (), (),
                            (), ());
                } else {
                    ("Unknown hyperlink!");
                }
                break;
            case MERGE:
                (
                        "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
                        (), (), (),
                        ());
                break;
            default:
        }
    }

    /**
     * 用日期去接字符串 肯定报错,此时需要用到异常处理
     * 在转换异常获取其他异常下会调用本接口。
     * 抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        ("解析失败,但是继续解析下一行:{}", ());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            ("第{}行,第{}列解析异常", (),
                    ());
        }
    }
}

4.创建自定义转换器

/**
 * 自定义转换器
 */
public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class supportJavaTypeKey() {
        return ;
    }
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return ;
    }
    /**
     * 读取调用
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
                                    GlobalConfiguration globalConfiguration) {
        return "自定义:" + ();
    }
    /**
     * 写入调用
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
                                       GlobalConfiguration globalConfiguration) {
        return new CellData(value);
    }
}

方法测试:

public class DemoMain {
    public static void main(String[] args) {
        String fileName = "D:\\";
        //读取单个sheet
        (fileName, , new DemoTestListener())
                // 需要读取批注 默认不读取
                .extraRead()
                // 需要读取超链接 默认不读取
                .extraRead()
                // 需要读取合并单元格信息 默认不读取
                .extraRead().sheet()
                .doRead();
        //多个sheet,读取全部
        // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写。
        (fileName, , new DemoTestListener()).doReadAll();

        //以下定义自定义格式转换
        (fileName, , new DemoTestListener())
                // 若全局使用自定义转换可以用registerConverter,所有java为string,excel为string的都会用这个转换器。
                // 如果就想单个字段使用请使用@ExcelProperty 指定converter
                // .registerConverter(new CustomStringStringConverter())
                .sheet()
                //如果多行头,可以设置其他值。默认第一行为行头。
                .headRowNumber(1)
                .doRead();

        //可以同步返回进行数据处理,但是不推荐,数据量大会放到内存里。
        List<ImportContent> list = (fileName).head().sheet().doReadSync();

    }
}

层接收上传的编写方式,如下:

    /**
     * 文件上传
     */
    @PostMapping("upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
        ((), , new UploadDataListener(uploadDAO)).sheet().doRead();
        return "success";
    }