当前位置: 首页 > news >正文

POI和EasyExcel

今天小编接到一个需求编写一个模块的导入和导出(导出Excel 和 导入Excel到数据库中)。想到做到就是学到,在实现需求的同时又学到相关的技术,岂不美哉~

操作 Excel 目前比较流行的就是 Apache POI 和 阿里巴巴的 easyExcel!

文章目录

    • POI 和 EasyExcel 概述
    • 1. POI-Excel 写操作
      • 1.1. POI-Excel 简单写操作
      • 1.2. POI-Excel 数据批量导入
    • 2. POI-Excel 读操作
      • 2.1. POI-Excel 简单读操作
      • 2.2. POI-Excel 读取不同类型的数据
    • 3. EasyExcel 写操作
      • 3.1.简单的写
      • 3.2.根据参数只导出指定列
      • 3.3.指定写入的列
      • 3.4.指定标题_日期、数字或者自定义格式转换、自定义行高列宽、表格样式
      • 3.5.web中的写
    • 4. EasyExcel 读操作
      • 4.1. EasyExcel 简单读操作
      • 4.2. EasyExcel Web读操作

POI 和 EasyExcel 概述

POI(Apache POI)

​ Apache POI是 Apache软件基金会的开放源码函数式子,POI提供API给Java程序对Microsofy Office格式档案读和写功能。

Apache POI 官网:https://poi.apache.org/

基本功能:

  • HSSF - 提供读写[Microsoft Excel](https://baike.baidu.com/item/Microsoft Excel?fromModule=lemma_inlink) XLS格式档案的功能。
  • XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能。
  • HWPF - 提供读写[Microsoft Word](https://baike.baidu.com/item/Microsoft Word?fromModule=lemma_inlink) DOC格式档案的功能。
  • HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
  • HDGF - 提供读[Microsoft Visio](https://baike.baidu.com/item/Microsoft Visio?fromModule=lemma_inlink)格式档案的功能。
  • HPBF - 提供读[Microsoft Publisher](https://baike.baidu.com/item/Microsoft Publisher?fromModule=lemma_inlink)格式档案的功能。
  • HSMF - 提供读[Microsoft Outlook](https://baike.baidu.com/item/Microsoft Outlook?fromModule=lemma_inlink)格式档案的功能。

​ 虽然POI是目前使用最多的用来做excel解析的框架,但这个框架并不那么完美。大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。

EasyExcel

​ EasyExcel 是阿里巴巴开源的一个Excel处理框架,以使用简单、节省内存著称。

​ 由于阿里的系统大多数都是大并发的情况下运行的,在大并发情况下,会发现poi存在一些bug,如果让POI团在大并发情况下会对其poi抛出一个异常。

​ EasyExcel 能大大减少占用内存的主要原因是在解析 Excel 时没有将文件数据一次性全部加载的内存中,而是从磁盘上一行行读取数据,逐个解析。

​ EasyExcel 官网:https://easyexcel.opensource.alibaba.com/

1、文件解压文件读取通过文件形式

2、避免将全部全部数据一次加载到内存

3、抛弃不重要的数据

Excel解析时候会包含样式,字体,宽度等数据,但这些数据是我们不关心的,如果将这部分数据抛弃可以大大降低内存使用。Excel中数据如下Style占了相当大的空间。

1. POI-Excel 写操作

首先我们来分析一下一个 Excel 文件包括哪几个部分~

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WYsoZpTk-1661847067993)(POI 和 EasyExcel.assets/image-20220830093928868.png)]

相关依赖:

<!-- xls(03) -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>

<!-- xlsx(07) -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>

1.1. POI-Excel 简单写操作

POI-Excel 简单写操作

首先我们来生成一个 03 版本的Excel~

private final String  PATH = "/Users/hgw/Documents/Data/java/easyExcelDemo/poi/src/main/resources/warehouse";
@Test
public void testWrite03() {
    // 1、创建一个工作簿
    Workbook workbook = new HSSFWorkbook();
    // 2、创建一个工作表
    Sheet sheet = workbook.createSheet("人员导入模版03");
    // 3、创建一个行
    Row row = sheet.createRow(0);
    // 4、创建多个单元格
    Cell cell1 = row.createCell(0); // 第一行第一个
    cell1.setCellValue("学号");
    Cell cell2 = row.createCell(1); // 第一行第二个
    cell2.setCellValue("姓名");

    // 5、创建多行多个单元格
    Row row2 = sheet.createRow(1);
    Cell row2Cell1 = row2.createCell(0); // 第二行第一个
    row2Cell1.setCellValue("2019110124");
    Cell row2Cell2 = row2.createCell(1); // 第二行第二个
    row2Cell2.setCellValue("言安");

    FileOutputStream fileOutputStream = null;
    // 6、生成一张表
    try {
        fileOutputStream = new FileOutputStream(PATH + "人员导入模版03.xls");
        workbook.write(fileOutputStream);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    System.out.println("人员导入模版03.xls生成完毕");
}

07的同03大致相同,使用 XSSFWorkbook 创建即可,并需要注意03的扩展名是.xls ,而07是.xlsx

@Test
public void testWrite07() {
    // 1、创建一个工作簿
    XSSFWorkbook workbook = new XSSFWorkbook();
    // 2、创建一个工作表
    Sheet sheet = workbook.createSheet("人员导入模版07");
    // 3、创建一个行
    Row row = sheet.createRow(0);
    // 4、创建多个单元格
    Cell cell1 = row.createCell(0); // 第一行第一个
    cell1.setCellValue("学号");
    Cell cell2 = row.createCell(1); // 第一行第二个
    cell2.setCellValue("姓名");

    // 5、创建多行多个单元格
    Row row2 = sheet.createRow(1);
    Cell row2Cell1 = row2.createCell(0); // 第二行第一个
    row2Cell1.setCellValue("2019110124");
    Cell row2Cell2 = row2.createCell(1); // 第二行第二个
    row2Cell2.setCellValue("言安");

    FileOutputStream fileOutputStream = null;
    // 6、生成一张表
    try {
        fileOutputStream = new FileOutputStream(PATH + "人员导入模版07.xlsx");
        workbook.write(fileOutputStream);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    System.out.println("人员导入模版07.xls生成完毕");
}

此时查看一下目录~
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hMIS1WAh-1661847150521)(POI 和 EasyExcel.assets/image-20220830100827590.png)]

1.2. POI-Excel 数据批量导入

大文件写 HSSF(03)

缺点:最多只能处理 65536行,否则会抛出异常

java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)

优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度快。

@Test
public void testWrite03BigData() {
    // 开始时间
    long begin = System.currentTimeMillis();

    // 创建表,批量数据
    Workbook workbook = new HSSFWorkbook();
    Sheet sheet = workbook.createSheet("批量写03");
    for (int rowNum = 0; rowNum < 65536; rowNum++) {
        Row row = sheet.createRow(rowNum);
        for (int cellNum = 0; cellNum < 10; cellNum++) {
            Cell cell = row.createCell(cellNum);
            cell.setCellValue(rowNum + cellNum);
        }
    }

    FileOutputStream outputStream = null;
    try {
        outputStream = new FileOutputStream(PATH + "批量写03.xls");
        workbook.write(outputStream);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 结束时间
    long end = System.currentTimeMillis();

    System.out.println("批量写03.xls 创建完毕" + (double)(end-begin)/1000);
}

运行时间:批量写03.xls 创建完毕1.477

大文件写 XSSF(07)

缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如 100 万条

优点:可以写较大的数据量,如20万条。

@Test
public void testWrite07BigData() {
    // 开始时间
    long begin = System.currentTimeMillis();

    // 创建表,批量数据
    Workbook workbook = new XSSFWorkbook();
    Sheet sheet = workbook.createSheet("批量写07");
    for (int rowNum = 0; rowNum < 65536; rowNum++) {
        Row row = sheet.createRow(rowNum);
        for (int cellNum = 0; cellNum < 10; cellNum++) {
            Cell cell = row.createCell(cellNum);
            cell.setCellValue(rowNum + cellNum);
        }
    }

    FileOutputStream outputStream = null;
    try {
        outputStream = new FileOutputStream(PATH + "批量写07.xlsx");
        workbook.write(outputStream);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 结束时间
    long end = System.currentTimeMillis();

    System.out.println("批量写07.xlsx 创建完毕" + (double)(end-begin)/1000);
}

运行时间:批量写07.xlsx 创建完毕5.154

如何写入更快呢?使用缓存~

大文件写 SXSSF

优点:可以写非常大的数据量,如100w条甚至更多条,写数据速度快,占用更少的内存。

注意:

  • 过程中会产生临时文件,需要清理临时文件 ((SXSSFWorkbook)workbook).dispose();
  • 默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件
  • 如果想自定义内存中数据的数量,可以使用 new SXSSFWorkbook(int rowAccessWindowSize)
@Test
public void testWrite07BigDataS() {
    // 开始时间
    long begin = System.currentTimeMillis();

    // 创建表,批量数据
    Workbook workbook = new SXSSFWorkbook();
    Sheet sheet = workbook.createSheet("批量写07");
    for (int rowNum = 0; rowNum < 65536; rowNum++) {
        Row row = sheet.createRow(rowNum);
        for (int cellNum = 0; cellNum < 10; cellNum++) {
            Cell cell = row.createCell(cellNum);
            cell.setCellValue(rowNum + cellNum);
        }
    }

    FileOutputStream outputStream = null;
    try {
        outputStream = new FileOutputStream(PATH + "批量写07Super.xlsx");
        workbook.write(outputStream);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            outputStream.close();
            // 清除临时文件!
            ((SXSSFWorkbook)workbook).dispose();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 结束时间
    long end = System.currentTimeMillis();

    System.out.println("批量写07Super.xlsx 创建完毕" + (double)(end-begin)/1000);
}

运行时间:批量写07Super.xlsx 创建完毕3.143


2. POI-Excel 读操作

2.1. POI-Excel 简单读操作

@Test
public void testRead03() {
    FileInputStream inputStream = null;
    try {
        // 1、获取文件输入流
        inputStream = new FileInputStream(PATH + "人员导入模版03.xls");
        // 2、得到工作簿
        Workbook workbook = new HSSFWorkbook(inputStream);
        // 3、得到表
        Sheet sheet = workbook.getSheetAt(0);
        // 4、得到行
        Row row1 = sheet.getRow(0);
        // 5、得到单元格
        Cell cell1 = row1.getCell(0);
        
        System.out.println(cell1.getStringCellValue());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意获取值的类型即可!读取值得时候注意数据类型,数据类型不匹配会报错。批量的话我们只需要创建个for循环接收即可!

@Test
public void testRead07() {
    FileInputStream inputStream = null;
    try {
        // 1、获取文件输入流
        inputStream = new FileInputStream(PATH + "人员导入模版07.xlsx");
        // 2、得到工作簿
        Workbook workbook = new XSSFWorkbook(inputStream);
        // 3、得到表
        Sheet sheet = workbook.getSheetAt(0);
        // 4、得到行
        Row row2 = sheet.getRow(1);
        // 5、得到单元格
        Cell cell2 = row2.getCell(0);

        System.out.println(cell2.getStringCellValue());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.2. POI-Excel 读取不同类型的数据

在使用场景中Excel表格中会存在多种不同类型的数据,我们可以先通过 getCellType 获取当前的单元格的数据类型,然后通过 switch 判断当前的数据类型去做不同的数据处理~

如下订单表中包括了字符串、日期、数字、公式等不同的数据类型:

在这里插入图片描述

private void readExcel(FileInputStream inputStream) {
    try {
        // 1、获取文件输入流
        inputStream = new FileInputStream(PATH + "订单表.xlsx");
        // 2、得到工作簿
        Workbook workbook = new XSSFWorkbook(inputStream);
        // 3、得到表
        Sheet sheet = workbook.getSheetAt(0);
        // 4、获取标题内容
        Row rowTitle = sheet.getRow(0);
        if (rowTitle != null) {
            int cellCount = rowTitle.getPhysicalNumberOfCells();
            for (int cellNum = 0; cellNum < cellCount; cellNum++) {
                Cell cell = rowTitle.getCell(cellNum);
                if (cell != null) {
                    CellType cellType = cell.getCellType();
                    String cellValue = cell.getStringCellValue();
                    System.out.print(cellValue + " | ");
                }
            }
            System.out.println();
        }
        // 5、获取表中内容(除第一行之外的记录)
        int numMergedRegions = sheet.getPhysicalNumberOfRows();  // 获取有多少行
        for (int rowNum = 1; rowNum < numMergedRegions; rowNum++) {         // 因为第一行是标题,所以从第二行开始 即下标为1
            Row rowData = sheet.getRow(rowNum);
            if (rowData != null) {
                // 读取列数
                int cellCount = rowData.getPhysicalNumberOfCells();
                for (int cellNum = 0; cellNum < cellCount; cellNum++) {
                    Cell cell = rowData.getCell(cellNum);
                    // 匹配列的数据类型
                    if (cell != null) {
                        CellType cellType = cell.getCellType();
                        Object cellValue = null;
                        switch (cellType) {
                            case _NONE: // 默认类型
                                cellValue = cell.getStringCellValue();
                                break;
                            case NUMERIC:   // 数字(日期、普通数字)
                                if (HSSFDateUtil.isCellDateFormatted(cell)) { // 日期
                                    cellValue = cell.getDateCellValue();
                                } else {    // 非日期
                                    cellValue = cell.getNumericCellValue();
                                }
                                break;
                            case STRING:   // 字符串
                                cellValue = cell.getStringCellValue();
                                break;
                            case BLANK:     // 空
                                break;
                            case BOOLEAN:   // 布尔
                                cellValue = cell.getBooleanCellValue();
                                break;
                            case ERROR:     // 错误
                                break;
                            case FORMULA:   // 公式
                                // 拿到计算公式
                                FormulaEvaluator formulaEvaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);
                                // 计算公式
                                cellValue = formulaEvaluator.evaluate(cell);
                                break;
                        }
                        System.out.print(cellValue + " | ");
                    }
                }
                System.out.println();
            }
        }

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

3. EasyExcel 写操作

在工作中POI用的很少了,接下来进入正题:EasyExcel~

首选导入依赖~

<!-- easyexcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
</dependency>

表格存放路径:

private final String PATH = "/Users/hgw/Documents/Data/java/easyExcelDemo/easyexceldemo/src/main/resources/warehouse/";

通用数据生成 后面不会重复写

private List<DemoData> data() {
    List<DemoData> list = ListUtils.newArrayList();
    for (int i = 0; i < 10; i++) {
        DemoData data = new DemoData();
        data.setString("字符串" + i);
        data.setDate(new Date());
        data.setDoubleData(0.56);
        list.add(data);
    }
    return list;
}

3.1.简单的写

Excel示例:

编写一个实体类:

/**
 * Description:
 * Data time:2022/8/30 14:01
 * Author:TQ-3038-HuangGongWei
 */
@Data
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}

最简单的写:

@Test
public void simpleWrite() {
    // 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入

    String fileName = PATH + "EasyTest.xlsx";
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    // 如果这里想使用03 则 传入excelType参数即可
    // write(fileName, 格式类)
    // sheet(表名)
    // doWrite(数据)
    EasyExcel.write(fileName, DemoData.class)
            .sheet("模板")
            .doWrite(() -> {
                // 分页查询数据
                return data();
            });
}

其他两种写法:

// 写法二
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
// 写法三
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
    WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
    excelWriter.write(data(), writeSheet);
}

3.2.根据参数只导出指定列

如:忽略 date

@Test
public void excludeOrIncludeWrite() {
    // 根据用户传入字段 假设我们要忽略 date
    String fileName = PATH + "根据参数忽略指定列模板.xlsx";
    Set<String> excludeColumnFiledNames = new HashSet<String>();
    excludeColumnFiledNames.add("date");
    EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板")
            .doWrite(data());
}

如:只导出 date

@Test
public void excludeOrIncludeWrite() {
    // 根据用户传入字段 假设我们只要导出 date
    fileName = PATH + "根据参数只导出指定列模板.xlsx";
    Set<String> includeColumnFiledNames = new HashSet<String>();
    includeColumnFiledNames.add("date");
    EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板")
            .doWrite(data());
}

3.3.指定写入的列

指定写入列,如下 第三列 为空~

@Data
public class IndexData {
    @ExcelProperty(value = "字符串标题", index = 0)
    private String string;
    @ExcelProperty(value = "日期标题", index = 1)
    private Date date;
    /**
     * 这里设置3 会导致第二列空的
     */
    @ExcelProperty(value = "数字标题", index = 3)
    private Double doubleData;
}
@Test
public void indexWrite() {
    String fileName = PATH +  "指定写入的列模板.xlsx";
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data());
}

3.4.指定标题_日期、数字或者自定义格式转换、自定义行高列宽、表格样式

Excel如下:

@Data
@ContentRowHeight(15)
@HeadRowHeight(20)
@ColumnWidth(15)
// 头背景设置成红色 IndexedColors.RED.getIndex()
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10)
// 头字体设置成20
@HeadFontStyle(fontHeightInPoints = 20)
// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()
@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17)
// 内容字体设置成20
@ContentFontStyle(fontHeightInPoints = 12)
public class User {
    /**
     * 我想所有的 字符串起前面加上"自定义:"三个字
     */
    @ExcelProperty({"用户表","姓名"})
    @Order(0)
    private String string;
    /**
     * 我想写到excel 用年月日的格式
     */
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    @ExcelProperty({"用户表","出生日期"})
    @Order(1)
    @ColumnWidth(25)
    private Date date;
    /**
     * 我想写到excel 用百分比表示
     */
    @NumberFormat("#.##%")
    @ExcelProperty({"用户表","成绩比"})
    @Order(2)
    private Double doubleData;
}

3.5.web中的写

首先引入所需要的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.78</version>
</dependency>

文件下载(失败了会返回一个有部分数据的Excel)

/**
 * 文件下载(失败了会返回一个有部分数据的Excel)
 * 1. 创建excel对应的实体对象
 * 2. 设置返回的 参数
 * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
 */
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
    // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("utf-8");
    // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
    String fileName = URLEncoder.encode("用户表模板", "UTF-8").replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    EasyExcel.write(response.getOutputStream(), User.class).sheet("模板").doWrite(data());
}

文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)

/**
 * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)
 *
 * @since 2.1.1
 */
@GetMapping("downloadFailedUsingJson")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
    // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
    try {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("用户表模板", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        // 这里需要设置不关闭流
        EasyExcel.write(response.getOutputStream(), User.class).autoCloseStream(Boolean.FALSE).sheet("模板")
                .doWrite(data());
    } catch (Exception e) {
        // 重置response
        response.reset();
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        Map<String, String> map = MapUtils.newHashMap();
        map.put("status", "failure");
        map.put("message", "下载文件失败" + e.getMessage());
        response.getWriter().println(JSON.toJSONString(map));
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-quaIHUCW-1661847360823)(POI 和 EasyExcel.assets/image-20220830152439206.png)]

4. EasyExcel 读操作

4.1. EasyExcel 简单读操作

首先编写一个Domin:

@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}

假设这个是你的DAO存储,这里不去具体写了~

/**
 * Description: 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
 */
public class DemoDAO {
    public void save(List<DemoData> list) {
        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
    }
}

具体的逻辑都是在监听器中编写的,这个每一条数据解析都会来调用 invoke

@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DemoDAO demoDAO;

    public DemoDataListener() {
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        demoDAO = new DemoDAO();
    }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param demoDAO
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    要读取的类型
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        demoDAO.save(cachedDataList);
        log.info("存储数据库成功!");
    }
}

编写简单的读:

@Test
public void simpleRead() {
    // 写法1:JDK8+ ,不用额外写一个DemoDataListener
    String fileName = PATH + "简单写模板.xlsx";
    // 这里每次会读取100条数据 然后返回过来 直接调用使用数据就行
    EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList 		 -> {
      for (DemoData demoData : dataList) {
        log.info("读取到一条数据{}", JSON.toJSONString(demoData));
      }
    })).sheet().doRead();
}

4.2. EasyExcel Web读操作

编写实体类:

@Data
public class UploadData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}

编写Dao:

@Repository
public class UploadDataDAO {
    public void save(List<UploadData> list) {
        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
    }
}

编写监听器,最简单的读的监听器 只是泛型变了

@Slf4j
public class UploadDataListener implements ReadListener<UploadData> {

    private static final int BATCH_COUNT = 100;
    private List<UploadData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    private UploadDataDAO uploadDataDAO;

    public UploadDataListener() {
        uploadDataDAO = new UploadDataDAO();
    }

    public UploadDataListener(UploadDataDAO uploadDataDAO) {
        this.uploadDataDAO = uploadDataDAO;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    要读取的类型
     * @param context
     */
    @Override
    public void invoke(UploadData data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        uploadDataDAO.save(cachedDataList);
        log.info("存储数据库成功!");
    }
}

编写Controller~

@RestController
public class DataImport {

    @Autowired
    private UploadDataDAO uploadDAO;

    /**
     * 文件上传
     * 1. 创建excel对应的实体对象
     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器
     * 3. 直接读即可
     */
    @PostMapping("upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead();
        return "success";
    }
}

相关文章:

  • LeetCode---SQL刷题4
  • 【SpringMVC】注解开发
  • 语言基础 - 1
  • Word发布到分类内测试1
  • 150000人疯抢的证书究竟是何方神圣?
  • android使用tcpdump抓包
  • UNITY与旷世Face++☀️二、手势识别的实现
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • golang promethus consul 服务发现
  • 五年后端开发,仅考这份面试题和答案,成功涨薪到30k!!!
  • ISE的仿真库编译步骤——基于Omapl138/TMS320C6748+FPGA核心板
  • sql2java-pagehelper:参照Mybatis-PageHelper实现分页查询
  • 质量平台-sonarlint-idea本地配置及使用技巧
  • ansible入门
  • 表单的语法及属性(form)
  • 2017 前端面试准备 - 收藏集 - 掘金
  • Apache Pulsar 2.1 重磅发布
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • Git学习与使用心得(1)—— 初始化
  • JavaScript DOM 10 - 滚动
  • JavaScript-Array类型
  • JavaScript学习总结——原型
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • spring boot下thymeleaf全局静态变量配置
  • TCP拥塞控制
  • 编写高质量JavaScript代码之并发
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 关于 Cirru Editor 存储格式
  • 罗辑思维在全链路压测方面的实践和工作笔记
  • 前端_面试
  • 使用docker-compose进行多节点部署
  • 我建了一个叫Hello World的项目
  • 学习笔记:对象,原型和继承(1)
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • 阿里云移动端播放器高级功能介绍
  • 国内开源镜像站点
  • 移动端高清、多屏适配方案
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (接口封装)
  • (学习日记)2024.01.19
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (一)为什么要选择C++
  • (转)memcache、redis缓存
  • (转)Oracle存储过程编写经验和优化措施
  • .NET delegate 委托 、 Event 事件,接口回调
  • .NET 材料检测系统崩溃分析
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .Net转前端开发-启航篇,如何定制博客园主题
  • .set 数据导入matlab,设置变量导入选项 - MATLAB setvaropts - MathWorks 中国
  • .sh
  • @RequestBody与@ResponseBody的使用
  • @RestControllerAdvice异常统一处理类失效原因
  • @拔赤:Web前端开发十日谈