如何讓excel文件讀取變得更簡單

  • 2019 年 10 月 22 日
  • 筆記

今天給大家安利一款excel文件導入神器,easyexcel,官方地址:(https://github.com/alibaba/easyexcel)。
在官網文檔中有介紹了其性能。




從上面的性能測試可以看出easyexcel在解析耗時上比poiuserModel模式弱了一些。主要原因是我內部採用了反射做模型字段映射,中間我也加了cache,但感覺這點差距可以接受的。但在內存消耗上差別就比較明顯了,easyexcel在後面文件再增大,內存消耗幾乎不會增加了。但poi userModel就不一樣了,簡直就要爆掉了。想想一個excel解析200M,同時有20個人再用估計一台機器就掛了。

如何使用呢

1、引入maven依賴

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

由於改jar包對poi進行了一些封裝,因此需要注釋掉項目引用的poi依賴,不然會有版本衝突。
2、創建接收excel數據的實體

@Data  public class Person {        @ExcelProperty(value = "姓名",index = 1)      private String name;        @ExcelProperty("性別")      private String sex;        @ExcelProperty("年齡")      private int age;  }

@ExcelProperty 這個註解用於指定該屬性對應excel文件中的哪一列數據。裏面有兩個屬性,一個是value,另一個是index(從0開始),這裡不建議 index 和 name 同時用,要麼一個對象只用index,要麼一個對象只用name去匹配。
3、增加person監聽器

@Slf4j  public class PersonListener extends AnalysisEventListener<Person> {        /**       * 每隔5條存儲數據庫,實際使用中可以3000條,然後清理list ,方便內存回收       */      private static final int BATCH_COUNT = 5;      List<Person> list = new ArrayList();        @Override      public void invoke(Person person, AnalysisContext analysisContext) {          log.info("解析到一條數據:{}",person);          if (list.size() >= BATCH_COUNT) {              saveData();              list.clear();          }      }        @Override      public void doAfterAllAnalysed(AnalysisContext analysisContext) {          saveData();          log.info("所有數據解析完成!");      }      @Override      public void onException(Exception exception, AnalysisContext context) {          log.error("解析失敗,但是繼續解析下一行", exception);        }      /**       * 加上存儲數據庫       */      private void saveData(){          log.info("{}條數據,開始存儲數據庫!", list.size());          log.info("存儲數據庫成功!");      }  }

4、文件上傳方法

@RestController  @Slf4j  public class PersonController {        @PostMapping("importFile")      public String readPerson(MultipartFile file){            PersonListener personListener = new PersonListener();          try {                // headRowNumber(2) 這裡可以設置1,因為頭就是一行。如果多行頭,可以設置其他值。不傳入也可以,他沒有指定頭,也就是默認1行              EasyExcel.read(file.getInputStream(), Person.class,personListener).sheet().headRowNumber(1).doRead();            }catch (IOException ioe){                log.info("讀取excel異常={}",ioe);          }          return "";      }  }

這樣代碼基本就算完成了。接下來我們看看如何在

spring框架中使用

我們知道,在spring中文件入庫的時候可能需要調用service,service調用dao入庫。那我們如何在這個PersonListener中調用service呢。
首先,在PersonController中注入service.

    @Autowired      private PersonService personService;

然後,在PersonListener中增加一個有參構造器。

   private PersonService personService;        public PersonListener(PersonService personService){            this.personService = personService;      }

最後在PersonController中new PersonListener的時候將service傳進來即可。

    @Autowired      private PersonService personService;      @PostMapping("importFile")      public String readPerson(MultipartFile file){            PersonListener personListener = new PersonListener(personService);          try {                // headRowNumber(2) 這裡可以設置1,因為頭就是一行。如果多行頭,可以設置其他值。不傳入也可以,他沒有指定頭,也就是默認1行              EasyExcel.read(file.getInputStream(), Person.class,personListener).sheet().headRowNumber(2).doRead();            }catch (IOException ioe){                log.info("讀取excel異常={}",ioe);          }          return "";      }

接下來我們在看一下,有一個需求是這樣的,我們需要統計一下導入成功數和失敗數並且要不失敗數寫入到一個excel中返回給頁面,如何實現?
我們調整一下PersonListener

@Slf4j  public class PersonListener extends AnalysisEventListener<Person> {        private int successCount = 0; // 成功量        private int exceptionCount = 0; // 異常量        private List<Person> exceptionList = new ArrayList<>(); // 異常數據        /**       * 每隔5條存儲數據庫,實際使用中可以3000條,然後清理list ,方便內存回收       */      private static final int BATCH_COUNT = 5;      List<Person> list = new ArrayList();        private PersonService personService;        public PersonListener(PersonService personService){            this.personService = personService;      }        @Override      public void invoke(Person person, AnalysisContext analysisContext) {          log.info("解析到一條數據:{}",person);          successCount++;                  list.add(person);          if (list.size() >= BATCH_COUNT) {              saveData();              list.clear();          }      }        @Override      public void doAfterAllAnalysed(AnalysisContext analysisContext) {          saveData();          log.info("所有數據解析完成!");      }      @Override      public void onException(Exception exception, AnalysisContext context) {          log.error("解析失敗,但是繼續解析下一行", exception);            Person person  = (Person)context.readRowHolder().getCurrentRowAnalysisResult();          exceptionList.add(person);          exceptionCount++;        }      /**       * 加上存儲數據庫       */      private void saveData(){          log.info("{}條數據,開始存儲數據庫!", list.size());          log.info("存儲數據庫成功!");      }      /**       * 插入結果返回       * @return       */      public Map<String,Object> getData(){            Map<String,Object> map = new HashMap<>();          map.put("success",successCount);          map.put("exception",exceptionCount);          return map;      }        /**       * 失敗數據返回       * @return       */      public List<Person> getExceptionList(){            return exceptionList;      }  }

在PersonController中將異常數據寫入文件。

@RestController  @Slf4j  public class PersonController {        @Autowired      private PersonService personService;        @PostMapping("importFile")      public String readPerson(MultipartFile file){            PersonListener personListener = new PersonListener(personService);          try {                // headRowNumber(1) 這裡可以設置1,因為頭就是一行。如果多行頭,可以設置其他值。不傳入也可以,他沒有指定頭,也就是默認1行              EasyExcel.read(file.getInputStream(), Person.class,personListener).sheet().headRowNumber(1).doRead();            }catch (IOException ioe){                log.info("讀取excel異常={}",ioe);          }          Map<String, Object> data = personListener.getData();          String exception = data.get("exception") + "";          int exceptionCount = Integer.parseInt(exception);          if(exceptionCount>0){              String fileName = System.currentTimeMillis() + ".xlsx";              // 這裡 需要指定寫用哪個class去讀,然後寫到第一個sheet,名字為模板 然後文件流會自動關閉              EasyExcel.write("E://upload/file/"+fileName, Person.class).sheet("模板").doWrite(personListener.getExceptionList());              data.put("fileName",fileName);          }          return data.toString();      }  }

我們寫個頁面測試一下

<!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>文件上傳</title>  </head>  <body>  <form action="/importFile" method="post" enctype="multipart/form-data">      <input type="file"  name="file"/>      <button type="submit">提交</button>  </form>  </body>  </html>

下面是測試的日誌文件

2019-10-20 11:21:59.809  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一條數據:Person(name=執偕, sex=男, age=18)  2019-10-20 11:21:59.811  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一條數據:Person(name=執偕2, sex=男, age=19)  2019-10-20 11:21:59.811  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一條數據:Person(name=執偕3, sex=男, age=20)  2019-10-20 11:21:59.811  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一條數據:Person(name=執偕4, sex=男, age=21)  2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一條數據:Person(name=執偕5, sex=男, age=22)  2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 5條數據,開始存儲數據庫!  2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 存儲數據庫成功!  2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一條數據:Person(name=執偕6, sex=男, age=23)  2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一條數據:Person(name=執偕7, sex=男, age=24)  2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一條數據:Person(name=執偕8, sex=男, age=25)  2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一條數據:Person(name=執偕9, sex=男, age=26)  2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 4條數據,開始存儲數據庫!  2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 存儲數據庫成功!  2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 所有數據解析完成!

今天就介紹到這了,文中的代碼我已上傳到碼雲上,
地址:https://gitee.com/javaXiaoCaiJi/zhixie-code-example/tree/master/easyexcel

如果文章對您有幫助,請記得點贊關注喲~
歡迎大家關注我的公眾號,每日技術推送文章供大家學習參考。

Exit mobile version