1. 程式人生 > >如何讓excel檔案讀取變得更簡單

如何讓excel檔案讀取變得更簡單

今天給大家安利一款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

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