IT评测·应用市场-qidao123.com

标题: 《SpringBoot》EasyExcel实现百万数据的导入导出 [打印本页]

作者: 卖不甜枣    时间: 2025-1-23 08:21
标题: 《SpringBoot》EasyExcel实现百万数据的导入导出
24年11月6日消息,阿里巴巴旗下的Java Excel工具库EasyExcel近日宣布,将停止更新,未来将逐步进入维护模式,将继续修复Bug,但不再主动新增功能。
EasyExcel 是一款着名的 Java Excel 工具库,由阿里巴巴开源,作者是玉箫,在 GitHub 上有 30k+ stars、7.5k forks。  据相识,EasyExcel作者玉箫去年已经从阿里离职,开始创业,也是开源数据库客户端 Chat2DB 的作者。
尽管 EasyExcel 已经不再维护,但其也不失为一个强大且优秀的工具框架,这里我们就一起看一下怎样使用 EasyExcel实现百万数据的导入导出。
简介

在一样平常的开发工作中,Excel 文件的读取和写入是非经常见的需求,特殊是在背景管理系统中更为频繁,基于传统的方式操作excel通常比较繁琐,EasyExcel 库的出现为我们带来了更简单、更高效的办理方案。本文将介绍 EasyExcel 库的基本用法和一些常见应用场景,帮助开发者更好地使用 EasyExcel 提高工作效率。
代码地点:https://github.com/alibaba/easyexcel
官网地点:Easy Excel 官网 (alibaba.com)
青出于蓝而胜于蓝

Java解析、天生Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以肯定程度的办理一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
16M内存23秒读取75M(46W行25列)的Excel(3.2.1+版本)
固然还有极速模式能更快,但是内存占用会在100M多一点 !

EasyExcel 核心类

EasyExcel 的核心类重要包括以下几个:
这些核心类在 EasyExcel 中负担了差别的脚色,协作完成了 Excel 文件的读取和写入操作。开发者可以根据具体的需求和场景,使用这些类来实现 Excel 文件的各种操作。
Alibaba EasyExcel的核心入口类是EasyExcel类,就想我们平时封装的Util类一样,通过它对excel数据读取或者导出。
技能方案

百万数据导入

以下代码源码点击这里

方案一

优点:实现简单,容易调试和维护;得当小数据量的场景。
缺点:效率极低,由于每次插入都要举行一次网络请求和磁盘I/O操作,且没有使用到批量操作的优势。在大量数据的环境下非常耗时。
  1. //方案一,方案二,方案四:单线程逐条解析
  2. public void importExcel(MultipartFile file) throws IOException {
  3.     EasyExcel.read(file.getInputStream(), Salaries.class, salariesListener).doReadAll();
  4. }
复制代码
EasyExcel解析完之后,都会回调ReadListener方法
  1. @Override
  2. @Transactional(rollbackFor = Exception.class)
  3. public void invoke(Salaries data, AnalysisContext context) {
  4.     //方案一:单线程逐行插入
  5.     saveOne(data);
  6. }
  7. public void saveOne(Salaries data) {
  8.     save(data);
  9.     logger.info("第" + count.getAndAdd(1) + "次插入1条数据");
  10. }
复制代码
方案二(保举)

优点:实现相对简单。比单条插入效率高,减少了网络请求和磁盘I/O的次数
缺点:受限于单线程的处理本领,效率不如多线程,并且在解析过程中可能会消耗大量内存,由于需要先将数据读入内存再举行批量插入。
  1. //方案一,方案二,方案四:单线程逐条解析
  2. public void importExcel(MultipartFile file) throws IOException {
  3.     EasyExcel.read(file.getInputStream(), Salaries.class, salariesListener).doReadAll();
  4. }
复制代码
  1. @Override
  2. @Transactional(rollbackFor = Exception.class)
  3. public void invoke(Salaries data, AnalysisContext context) {
  4.     salariesList.get().add(data);
  5.     if (salariesList.get().size() >= batchSize) {
  6.                 saveData();
  7.     }
  8. }
  9. public void saveData() {
  10.     if (!salariesList.get().isEmpty()) {
  11.         saveBatch(salariesList.get(), salariesList.get().size());
  12.         logger.info("第" + count.getAndAdd(1) + "次插入" + salariesList.get().size() + "条数据");
  13.         salariesList.get().clear();
  14.     }
  15. }
复制代码
方案三

优点:多线程解析可以充分使用多核CPU的优势,提高解析速度;批量插入可以减少数据库的变乱开销和I/O操作。
缺点:需要考虑多线程之间的数据传输和同步的问题,设计和实现较复杂。可能在解析和插入之间存在性能瓶颈。比方,若插入较快,则需要等待解析完成才可举行插入,因此可用异步线程的方式
  1. //方案三,方案四:多线程解析,一个sheet对应一个线程
  2. public void importExcelAsync(MultipartFile file) {
  3.     // 开20个线程分别处理20个sheet
  4.     List < Callable < Object >> tasks = new ArrayList < > ();
  5.     for (int i = 0; i < 20; i++) {
  6.         int num = i;
  7.         tasks.add(() - > {
  8.             EasyExcel.read(file.getInputStream(), Salaries.class, salariesListener)
  9.                 .sheet(num)
  10.                 .doRead();
  11.             return null;
  12.         });
  13.     }
  14.     try {
  15.         executorService.invokeAll(tasks);
  16.     } catch (InterruptedException e) {
  17.         throw new RuntimeException(e);
  18.     }
  19. }
复制代码
  1. @Override
  2. @Transactional(rollbackFor = Exception.class)
  3. public void invoke(Salaries data, AnalysisContext context) {
  4.     //方案二、三、四、五
  5.     salariesList.get().add(data);
  6.     if (salariesList.get().size() >= batchSize) {
  7.                 saveData();
  8.     }
  9. }
复制代码
方案四(较保举)

优点:解析和插入操作分离,可以在解析的同时举行插入,提高效率。
缺点:受限于单线程解析速度,而且需要管理异步操作
  1. //方案一,方案二,方案四:单线程逐条解析
  2. public void importExcel(MultipartFile file) throws IOException {
  3.     EasyExcel.read(file.getInputStream(), Salaries.class, salariesListener).doReadAll();
  4. }
复制代码
  1. @Override
  2. @Transactional(rollbackFor = Exception.class)
  3. public void invoke(Salaries data, AnalysisContext context) {
  4.     salariesList.get().add(data);
  5.     if (salariesList.get().size() >= batchSize) {
  6.                 asyncSaveData1();//异步线程批量插入
  7.     }
  8. }
复制代码
方案五(保举)

优点:联合了多线程解析和多线程异步插入的优势,可以最大化使用系统资源,提高数据导入速度。
缺点:实现较为复杂。需要小心处理数据库的并发毗连和变乱管理,防止引入死锁和性能瓶颈。
  1. //方案三,方案四:多线程解析,一个sheet对应一个线程
  2. public void importExcelAsync(MultipartFile file) {
  3.     // 开20个线程分别处理20个sheet
  4.     List < Callable < Object >> tasks = new ArrayList < > ();
  5.     for (int i = 0; i < 20; i++) {
  6.         int num = i;
  7.         tasks.add(() - > {
  8.             EasyExcel.read(file.getInputStream(), Salaries.class, salariesListener)
  9.                 .sheet(num)
  10.                 .doRead();
  11.             return null;
  12.         });
  13.     }
  14.     try {
  15.         executorService.invokeAll(tasks);
  16.     } catch (InterruptedException e) {
  17.         throw new RuntimeException(e);
  18.     }
  19. }
复制代码
  1. @Override
  2. @Transactional(rollbackFor = Exception.class)
  3. public void invoke(Salaries data, AnalysisContext context) {
  4.     salariesList.get().add(data);
  5.     if (salariesList.get().size() >= batchSize) {
  6.                 asyncSaveData();//多线程批量插入
  7.     }
  8. }
  9. public void asyncSaveData() {
  10.     if (!salariesList.get().isEmpty()) {
  11.         ArrayList < Salaries > salaries = (ArrayList < Salaries > ) salariesList.get().clone();
  12.         executorService.execute(new SaveTask(salaries, salariesListener));
  13.         salariesList.get().clear();
  14.     }
  15. }
复制代码
方案选择

在选择方案4时,需要特殊注意以下几点:
总之,方案4可能在数据量较大时更有优势,由于它可以更有效地使用系统资源,但在数据量不大时,这种优势可能不明显,反而增加了实现的复杂性和维护成本。因此,在选择方案时,需要根据实际的数据量、系统资源和性能需求来做出决策。但如果数据量较大,并且只有一个sheet,只能逐行解析时,较保举使用方案4
在选择方案5时,需要特殊注意以下几点:
综合考虑性能和实现复杂性,方案5 是处理百万级数据导入的最佳选择,条件是能合理管理多线程和数据库毗连。
百万数据导出


方案一

优点:实现简单,逻辑清晰;适用于数据量小的环境。
缺点:
  1. public void exportExcel1(HttpServletResponse response) throws IOException {
  2.     setExportHeader(response);
  3.     //查出所有数据
  4.     List < Salaries > salaries = salariesMapper.selectList(null);
  5.     EasyExcel.write(response.getOutputStream(), Salaries.class)
  6.         .sheet() //写到一个sheet
  7.         .doWrite(salaries);
  8. }
复制代码
方案二

优点:办理了单个 sheet 的行数限定问题。
缺点:
  1. public void exportExcel2(HttpServletResponse response) throws IOException {
  2.     setExportHeader(response);
  3.     //查出所有数据
  4.     List < Salaries > salaries = salariesMapper.selectList(null);
  5.     try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), Salaries.class).build()) {
  6.         //创建3个sheet
  7.         WriteSheet writeSheet1 = EasyExcel.writerSheet(1, "模板1").build();
  8.         WriteSheet writeSheet2 = EasyExcel.writerSheet(2, "模板2").build();
  9.         WriteSheet writeSheet3 = EasyExcel.writerSheet(3, "模板3").build();
  10.         //将查出的数据进行分割
  11.         List < Salaries > data1 = salaries.subList(0, salaries.size() / 3);
  12.         List < Salaries > data2 = salaries.subList(salaries.size() / 3, salaries.size() * 2 / 3);
  13.         List < Salaries > data3 = salaries.subList(salaries.size() * 2 / 3, salaries.size());
  14.         //写入3个sheet
  15.         excelWriter.write(data1, writeSheet1);
  16.         excelWriter.write(data2, writeSheet2);
  17.         excelWriter.write(data3, writeSheet3);
  18.     }
  19. }
复制代码
方案三(保举)

表明:
优点:
缺点:
  1. public void exportExcel3(HttpServletResponse response) throws IOException {
  2.     setExportHeader(response);
  3.     try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), Salaries.class).build()) {
  4.         //查询表数据条数
  5.         Long count = salariesMapper.selectCount(null);
  6.         Integer pages = 10; //定义分成10页数据
  7.         Long size = count / pages; //每页条数
  8.         for (int i = 0; i < pages; i++) {
  9.             //pages 页条数据,就创建pages页个 sheet
  10.             WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
  11.             Page < Salaries > page = new Page < > ();
  12.             page.setCurrent(i + 1);
  13.             page.setSize(size);
  14.             Page < Salaries > selectPage = salariesMapper.selectPage(page, null);
  15.             excelWriter.write(selectPage.getRecords(), writeSheet);
  16.         }
  17.     }
  18. }
复制代码
方案四(保举)

优点:
缺点:
额外说一点:easyexcel 是不支持并发写入多个sheet,只能一个sheet一个sheet的写。因此尽管是多线程分页查询了,也只能单线程写入同一个excel
  1. public void exportExcel4(HttpServletResponse response) throws IOException, InterruptedException {
  2.     setExportHeader(response);
  3.     //查询表数据条数
  4.     Long count = salariesMapper.selectCount(null);
  5.     Integer pages = 20; //定义分成10页数据
  6.     Long size = count / pages; //每页条数
  7.     //创建pages个线程
  8.     ExecutorService executorService = Executors.newFixedThreadPool(pages);
  9.     CountDownLatch countDownLatch = new CountDownLatch(pages);
  10.     Map < Integer, Page < Salaries >> pageMap = new HashMap < > ();
  11.     for (int i = 0; i < pages; i++) {
  12.         int finalI = i;
  13.         executorService.submit(new Runnable() {@
  14.             Override
  15.             public void run() {
  16.                 //多线程分页查询
  17.                 Page < Salaries > page = new Page < > ();
  18.                 page.setCurrent(finalI + 1);
  19.                 page.setSize(size);
  20.                 Page < Salaries > selectPage = salariesMapper.selectPage(page, null);
  21.                 pageMap.put(finalI, selectPage);
  22.                 countDownLatch.countDown();
  23.             }
  24.         });
  25.     }
  26.     countDownLatch.await();
  27.     try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), Salaries.class).build()) {
  28.         //easyexcel不支持并发写入多个sheet,只能一个sheet一个sheet的写
  29.         for (Map.Entry < Integer, Page < Salaries >> entry: pageMap.entrySet()) {
  30.             Integer num = entry.getKey();
  31.             Page < Salaries > salariesPage = entry.getValue();
  32.             WriteSheet writeSheet = EasyExcel.writerSheet(num, "模板" + num).build();
  33.             //写入多个sheet
  34.             excelWriter.write(salariesPage.getRecords(), writeSheet);
  35.         }
  36.     }
  37.     // https://github.com/alibaba/easyexcel/issues/1040
  38. }
复制代码
方案选择

对于百万级数据的导出,建议选择方案 3 或方案 4:
综上所述,方案 3 是一个兼顾实现复杂度和性能的选择,而方案 4 可以在资源充足(同样是全表放入内存中)且需要高性能的环境下使用。根据具体需求和系统资源,选择最符合的方案。
注意点

需要注意的是,分布式环境下,可能存在的问题:
以上问题可以通过 分布式锁来实现
模板方法设计模式简化EasyExcel的读取

官方文档 上读取Excel挺简单的,只需要一行代码
  1. /**
  2. * 指定列的下标或者列名
  3. *
  4. * <p>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
  5. * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
  6. * <p>3. 直接读即可
  7. */
  8. @Test
  9. public void indexOrNameRead() {
  10.     String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
  11.     // 这里默认读取第一个sheet
  12.     EasyExcel.read(fileName, DemoData.class, new  DemoDataListener()).sheet().doRead();
  13. }
复制代码
EasyExcel.read 团体流程图如下:

但仔细看,其实这里还需要创建一个回调监听器 DemoDataListener,也就是针对每个DemoData 即每个Excel 都需要创建一个单独的回调监听器类。
  1. // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
  2. @Slf4j
  3. public class DemoDataListener implements ReadListener<DemoData> {
  4.     /**
  5.      * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
  6.      */
  7.     private static final int BATCH_COUNT = 100;
  8.     /**
  9.      * 缓存的数据
  10.      */
  11.     private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
  12.     /**
  13.      * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
  14.      */
  15.     private DemoDataDAO demoDAO;
  16.     public DemoDataListener() {
  17.         // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
  18.         demoDAO = new DemoDAO();
  19.     }
  20.     /**
  21.      * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
  22.      *
  23.      * @param demoDAO
  24.      */
  25.     public DemoDataListener(DemoDataDAO demoDAO) {
  26.         this.demoDAO = demoDAO;
  27.     }
  28.     /**
  29.      * 这个每一条数据解析都会来调用
  30.      *
  31.      * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
  32.      * @param context
  33.      */
  34.     @Override
  35.     public void invoke(DemoData data, AnalysisContext context) {
  36.         log.info("解析到一条数据:{}", JSON.toJSONString(data));
  37.         cachedDataList.add(data);
  38.         // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
  39.         if (cachedDataList.size() >= BATCH_COUNT) {
  40.             saveData();
  41.             // 存储完成清理 list
  42.             cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
  43.         }
  44.     }
  45.     /**
  46.      * 所有数据解析完成了 都会来调用
  47.      *
  48.      * @param context
  49.      */
  50.     @Override
  51.     public void doAfterAllAnalysed(AnalysisContext context) {
  52.         // 这里也要保存数据,确保最后遗留的数据也存储到数据库
  53.         saveData();
  54.         log.info("所有数据解析完成!");
  55.     }
  56.     /**
  57.      * 加上存储数据库
  58.      */
  59.     private void saveData() {
  60.         log.info("{}条数据,开始存储数据库!", cachedDataList.size());
  61.         demoDAO.save(cachedDataList);
  62.         log.info("存储数据库成功!");
  63.     }
  64. }
复制代码
在使用EasyExcel读取Excel时就在想能够怎样简化读取方式,而不是读取每个Excel都创建一个XXDataListener 的监听器类
首先,可以把DataListener加上泛型,共用一个DataListener
看上面代码,只需要在new的时间再去new DataListener即可
但是,如果要通报Dao 和 并且每个Dao怎样保存数据,而且保存数据前可能还需要对数据举行校验,也就存在以下问题:
那么该怎样处理呢?
末了想到了可以用Function(数据校验) + Consumer(数据存储) + 模板方法设计模式,创建一个共用的EasyExcel读取监听器,从而不在监听器中对数据举行处理,把处理都前置
EasyExcel 的监听器类 Listener 已经定义了每一步会做什么,如通过 invokeHead 方法一行一行读取表头数据,通过invoke 方法一行一行读取真实数据。
也就是说,我们已经知道了 Listener 类所需的关键步调,即一行一行读取数据,而且确定了这些步调的实行顺序,但表头数据的校验和处理,真实数据的校验和处理还无法确定;并且这些是由导入的具体数据决定的,差别的表会有差别的校验 和 处理方式。基于此,我们就可以想到用 模板方法模式
代码详情如下:
  1. import cn.hutool.core.util.StrUtil;
  2. import com.alibaba.excel.context.AnalysisContext;
  3. import com.alibaba.excel.metadata.data.ReadCellData;
  4. import com.alibaba.excel.read.listener.ReadListener;
  5. import com.alibaba.excel.util.ConverterUtils;
  6. import lombok.extern.slf4j.Slf4j;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. import java.util.Map;
  10. import java.util.function.Consumer;
  11. import java.util.function.Function;
  12. @Slf4j
  13. public class EasyExcelListener<T> implements ReadListener<T> {
  14.     private static final int defaultBatchSize = 5000;
  15.     private final int batchSize;
  16.     private List<Map<Integer, String>> headData;
  17.     private List<T> realData;
  18.     private final int headRowCount;
  19.     private Function<Map<Integer, String>, String> headDataCheck;
  20.     private final Consumer<List<Map<Integer, String>>> headDataConsumer;
  21.     private Function<T, String> realDataCheck;
  22.     private final Consumer<List<T>> realDataConsumer;
  23.     private final List<String> errorList;
  24.     /**
  25.      * 构造函数
  26.      *
  27.      * @param headRowCount     表头行数
  28.      * @param headDataCheck    表头数据校验
  29.      * @param headDataConsumer 表头数据处理
  30.      * @param realDataCheck    真实数据校验
  31.      * @param realDataConsumer 真实数据处理
  32.      */
  33.     public EasyExcelListener(int headRowCount, Function<Map<Integer, String>, String> headDataCheck, Consumer<List<Map<Integer, String>>> headDataConsumer
  34.             , Function<T, String> realDataCheck, Consumer<List<T>> realDataConsumer) {
  35.         this.headRowCount = headRowCount;
  36.         this.batchSize = defaultBatchSize;
  37.         this.headDataCheck = headDataCheck;
  38.         this.headDataConsumer = headDataConsumer;
  39.         this.realDataCheck = realDataCheck;
  40.         this.realDataConsumer = realDataConsumer;
  41.         headData = new ArrayList<>(headRowCount);
  42.         realData = new ArrayList<>(batchSize);
  43.         errorList = new ArrayList<>();
  44.     }
  45.     public EasyExcelListener(int headRowCount, int batchSize, Function<Map<Integer, String>, String> headDataCheck, Consumer<List<Map<Integer, String>>> headDataConsumer
  46.             , Function<T, String> realDataCheck, Consumer<List<T>> realDataConsumer) {
  47.         this.headRowCount = headRowCount;
  48.         this.batchSize = batchSize;
  49.         this.headDataCheck = headDataCheck;
  50.         this.headDataConsumer = headDataConsumer;
  51.         this.realDataCheck = realDataCheck;
  52.         this.realDataConsumer = realDataConsumer;
  53.         headData = new ArrayList<>(headRowCount);
  54.         realData = new ArrayList<>(batchSize);
  55.         errorList = new ArrayList<>();
  56.     }
  57.     @Override
  58.     public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
  59.         //获取表头数据
  60.         Map<Integer, String> map = ConverterUtils.convertToStringMap(headMap, context);
  61.         //校验表头数据
  62.         String checkRes = headDataCheck.apply(map);
  63.         if (StrUtil.isNotBlank(checkRes)) {
  64.             int rowIndex = context.readRowHolder().getRowIndex() + 1;
  65.             errorList.add("行号为:" + rowIndex + checkRes);
  66.             return;
  67.         }
  68.         if (!errorList.isEmpty()) {
  69.             //有错误信息,就不会再处理,只做校验
  70.             return;
  71.         }
  72.          
  73.         //没有问题的数据添加进list中
  74.         headData.add(map);
  75.         if (context.readRowHolder().getRowIndex() == headRowCount - 1) {
  76.             //全部读取完表头数据后,处理表头数据
  77.             headDataConsumer.accept(headData);
  78.         }
  79.     }
  80.     @Override
  81.     public void invoke(T data, AnalysisContext context) {
  82.         //判断真实数据是否符合要求
  83.         String checkRes = realDataCheck.apply(data);
  84.         if (StrUtil.isNotBlank(checkRes)) {
  85.             int rowIndex = context.readRowHolder().getRowIndex() + 1;
  86.             errorList.add("行号为:" + rowIndex + checkRes);
  87.             return;
  88.         }
  89.         //没有报错的数据添加进list
  90.         realData.add(data);
  91.         if (realData.size() >= batchSize) {
  92.             //处理真实数据
  93.             realDataConsumer.accept(realData);
  94.             //清空realData
  95.             realData = new ArrayList<>(batchSize);
  96.         }
  97.     }
  98.     @Override
  99.     public void doAfterAllAnalysed(AnalysisContext context) {
  100.         // 解析完所有excel行, 剩余的数据还需要进行处理
  101.         realDataConsumer.accept(realData);
  102.     }
  103.     public List<Map<Integer, String>> getHeadData() {
  104.         return headData;
  105.     }
  106.     public List<T> getRealData() {
  107.         return realData;
  108.     }
  109.     public List<String> getErrorList() {
  110.         return errorList;
  111.     }
  112. }
复制代码
EasyExcel读取封装后的使用示例:
  1. //构建 EasyExcelListener
  2. EasyExcelListener < TaskImportDTO > taskImportDTOEasyExcelListener =
  3.     new EasyExcelListener < > (
  4.         4, //表头行数
  5.         (map) - > { //表头数据校验
  6.             return checkMap(map, startTime, taskMainDOList);
  7.         }, headList - > {
  8.             //表头数据处理   
  9.             saveHeadData(headList);
  10.         }, (data) - > {
  11.             //真实数据校验
  12.             return checkImportData(data);
  13.         }, realDataList - > {
  14.             //真实数据处理
  15.             saveRealData(realDataList);
  16.         }
  17.     );
  18. //读取数据
  19. EasyExcelUtil.read(inputStream, TaskImportDTO.class, taskImportDTOEasyExcelListener)
  20.     .headRowNumber(4) //表头行数
  21.     .sheet()
  22.     .doRead();
复制代码
上面的 checkMap ,saveHeadData,checkImportData,saveRealData皆为针对本次导入数据自定义的校验和处理方法。
若还有别的导入需求,只需new EasyExcelListener 方法,并自定义自己的校验和处理逻辑即可,从而完成代码复用!
替换产物

有个好消息就是,EasyExcel的作者创建了新项目:FastExcel。
开源地点:https://github.com/CodePhiliaX/fastexcel
作者选择为它起名为 FastExcel,以突出这个框架在处理 Excel 文件时的高性能表现,而不仅仅是简单易用。
FastExcel 将始终坚持免费开源,并采用最开放的 MIT 协议,使其适用于任何商业化场景。这为开发者和企业提供了极大的自由度和灵活性。FastExcel 的一些明显特点包括:
他们计划在未来推出更多新特性,以不绝提拔用户体验和工具实用性。FastExcel 致力于成为处理 Excel 文件的最佳选择。
重要特性:
当前 FastExcel 底层使用 poi 作为基础包,如果项目中已经有 poi 相关组件,需要手动排除 poi 的相关 jar 包。
如果使用 Maven 举行项目构建,请在 pom.xml 文件中引入以下设置:
  1. <dependency>    
  2.         <groupId>cn.idev.excel</groupId>    
  3.         <artifactId>fastexcel</artifactId>    
  4.         <version>1.0.0</version>
  5. </dependency>
复制代码
EasyExcel 与 FastExcel 的区别:
修改依赖

将 EasyExcel 的依赖替换为 FastExcel 的依赖,如下:
  1. <dependency>    
  2.         <groupId>com.alibaba</groupId>    
  3.         <artifactId>easyexcel</artifactId>    
  4.         <version>xxxx</version>
  5. </dependency>
复制代码
依赖替换为
  1. <dependency>    
  2.         <groupId>cn.idev.excel</groupId>    
  3.         <artifactId>fastexcel</artifactId>    
  4.         <version>1.0.0</version>
  5. </dependency>
复制代码
修改代码

将 EasyExcel 的包名替换为 FastExcel 的包名,如下:
  1. import com.alibaba.excel.**;
复制代码
替换为
  1. import cn.idev.excel.**;
复制代码
不修改代码直接依赖 FastExcel

如果由于种种原因不想修改代码,可以直接依赖 FastExcel ,然后在 pom.xml 文件中直接依赖 FastExcel。EasyExcel 与 FastExcel 可以共存,但是长期建议替换为 FastExcel。
建议以后使用 FastExcel 类

为了兼容性考虑保存了 EasyExcel 类,但是建议以后使用 FastExcel 类,FastExcel 类是FastExcel 的入口类,功能包含了 EasyExcel 类的所有功能,以后新特性仅在 FastExcel 类中添加。
简单示例:读取 Excel 文件 下面是读取 Excel 文档的例子:
  1. // 实现 ReadListener 接口,设置读取数据的操作  
  2. public class DemoDataListener implements ReadListener<DemoData> {  
  3.     @Override  
  4.     public void invoke(DemoData data, AnalysisContext context) {  
  5.         System.out.println("解析到一条数据" + JSON.toJSONString(data));  
  6.     }  
  7.   
  8.     @Override  
  9.     public void doAfterAllAnalysed(AnalysisContext context) {  
  10.         System.out.println("所有数据解析完成!");  
  11.     }  
  12. }  
  13.   
  14. public static void main(String[] args) {  
  15.     String fileName = "demo.xlsx";  
  16.     // 读取 Excel 文件  
  17.     FastExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();  
  18. }
复制代码
简单示例:创建 Excel 文件 下面是一个创建 Excel 文档的简单例子:
[code]// 示例数据类  public class DemoData {      @ExcelProperty("字符串标题")      private String string;      @ExcelProperty("日期标题")      private Date date;      @ExcelProperty("数字标题")      private Double doubleData;      @ExcelIgnore      private String ignore;  }    // 填充要写入的数据  private static List data() {      List list = new ArrayList();      for (int i = 0; i 




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4