阿里EasyExcel開源超簡單入門操做Excel

前段時間項目上須要導入一個3000多條數據的Excel耗時十幾分鍾,使用的是傳統的Apache poi的,最後統一決定用阿里的開源easy excel。
阿里在18年3月份左右發佈easyexcel,剛發佈就是打着「低內存」解決POI的oom的口號,本人在測試過程當中發現相比poi,確實在使用的體驗、操做簡便性上有很多提高,感謝阿里團隊的努力,官方給出的項目demp的github地址:https://github.com/alibaba/easyexceljava

easyexcel核心功能

  • 讀任意大小的0三、07版Excel不會OOM
  • 讀Excel自動經過註解,把結果映射爲java模型
  • 讀Excel支持多sheet
  • 讀Excel時候是否對Excel內容作trim()增長容錯
  • 寫小量數據的03版Excel(不要超過2000行)
  • 寫任意大07版Excel不會OOM
  • 寫Excel經過註解將表頭自動寫入Excel
  • 寫Excel能夠自定義Excel樣式 如:字體,加粗,表頭顏色,數據內容顏色
  • 寫Excel到多個不一樣sheet
  • 寫Excel時一個sheet能夠寫多個Table
  • 寫Excel時候自定義是否須要寫表頭

SpringBoot集成

一、pom依賴引入

<!--阿里easyexcel-->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>easyexcel</artifactId>
   <version>1.1.2-beta5</version>
</dependency>

二、ExcelUtil工具類

package com.example.mybaties.utils;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.util.StringUtils;
import com.example.mybaties.entity.UserExcelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @DESCRIPTION 阿里Excel工具類
 * @Author lst
 * @Date 2020-04-28 11:30
 */
@Slf4j
public class ExcelUtil {

    private static Sheet initSheet;

    /**
      * 默認初始化
     */
    static {
        //sheetNo: sheet頁碼,默認爲1
        //headLineMun: 從第幾行開始讀取數據,默認爲0, 表示從第一行開始讀取
        initSheet = new Sheet(1, 0);
        initSheet.setSheetName("sheet");
        //設置自適應寬度
        initSheet.setAutoWidth(Boolean.TRUE);
    }

    /**
      * @Description: 導入方法,讀取少於1000行數據
      * @author: lst
      * @date: 2020-4-28 11:26
      * @param filePath 文件絕對路徑
      * @return List<Object>
     */
    public static List<Object> readLessThan1000Row(String filePath){
        return readLessThan1000RowBySheet(filePath,null);
    }

    /**
      * @Description: 導入方法,讀小於1000行數據, 帶樣式
      * @author: lst
      * @date: 2020-4-28 11:27
      * @param filePath 文件絕對路徑
      * @param sheet sheet對象
      *      sheetNo: sheet頁碼,默認爲1
      *      headLineMun: 從第幾行開始讀取數據,默認爲0, 表示從第一行開始讀取
      *      clazz: 返回數據List<Object> 中Object的類名
      * @return List<Object>
     */
    public static List<Object> readLessThan1000RowBySheet(String filePath, Sheet sheet){
        if(!StringUtils.hasText(filePath)){
            return null;
        }
        //判斷是否使用默認的sheet
        sheet = sheet != null ? sheet : initSheet;

        InputStream fileStream = null;
        try {
            fileStream = new FileInputStream(filePath);
            return EasyExcelFactory.read(fileStream, sheet);
        } catch (FileNotFoundException e) {
            log.info("找不到文件或文件路徑錯誤, 文件:{}", filePath);
        }finally {
            try {
                if(fileStream != null){
                    fileStream.close();
                }
            } catch (IOException e) {
                log.info("excel文件讀取失敗, 失敗緣由:{}", e);
            }
        }
        return null;
    }

    /**
      * @Description  導入方法,讀大於1000行數據
      * @author lst
      * @date 2020-4-28 11:32
      * @param filePath 文件絕對路徑
      * @return List<Object>
     */
    public static List<Object> readMoreThan1000Row(String filePath){
        return readMoreThan1000RowBySheet(filePath,null);
    }

    /**
      * @Description  導入方法,讀大於1000行數據, 帶樣式
      * @author lst
      * @date 2020-4-28 11:33
      * @param filePath 文件絕對路徑
      * @param sheet sheet對象
      *      sheetNo: sheet頁碼,默認爲1
      *      headLineMun: 從第幾行開始讀取數據,默認爲0, 表示從第一行開始讀取
      *      clazz: 返回數據List<Object> 中Object的類名
      * @return List<Object>
     */
    public static List<Object> readMoreThan1000RowBySheet(String filePath, Sheet sheet){
        if(!StringUtils.hasText(filePath)){
            return null;
        }
        //判斷是否使用默認的sheet
        sheet = sheet != null ? sheet : initSheet;

        InputStream fileStream = null;
        try {
            fileStream = new FileInputStream(filePath);
            ExcelListenerPlus excelListener = new ExcelListenerPlus();
            EasyExcelFactory.readBySax(fileStream, sheet, excelListener);
            return excelListener.getDatas();
        } catch (FileNotFoundException e) {
            log.error("找不到文件或文件路徑錯誤, 文件:{}", filePath);
        }finally {
            try {
                if(fileStream != null){
                    fileStream.close();
                }
            } catch (IOException e) {
                log.error("excel文件讀取失敗, 失敗緣由:{}", e);
            }
        }
        return null;
    }

    /**
      * @Description 導出方法,生成excle
      * @author lst
      * @date 2020-4-28 11:37
      * @param filePath  絕對路徑, 如:/home/chenmingjian/Downloads/aaa.xlsx
      * @param data 數據源
      * @param head 表頭
     */
    public static void writeBySimple(String filePath, List<List<Object>> data, List<String> head){
        writeSimpleBySheet(filePath,data,head,null);
    }

    /**
      * @Description  導出方法,生成excle
      * @author lst
      * @date 2020-4-28 11:37
      * @param filePath 絕對路徑, 如:/home/chenmingjian/Downloads/aaa.xlsx
      * @param data 數據源
      * @param sheet excle頁面樣式
      * @param head 表頭
     */
    public static void writeSimpleBySheet(String filePath, List<List<Object>> data, List<String> head, Sheet sheet){
        //判斷是否使用默認的sheet
        sheet = (sheet != null) ? sheet : initSheet;

        if(head != null){
            List<List<String>> list = new ArrayList<>();
            head.forEach(h -> list.add(Collections.singletonList(h)));
            sheet.setHead(list);
        }

        OutputStream outputStream = null;
        ExcelWriter writer = null;
        try {
            outputStream = new FileOutputStream(filePath);
            writer = EasyExcelFactory.getWriter(outputStream);
            writer.write1(data,sheet);
        } catch (FileNotFoundException e) {
            log.error("找不到文件或文件路徑錯誤, 文件:{}", filePath);
        }finally {
            try {
                if(writer != null){
                    writer.finish();
                }

                if(outputStream != null){
                    outputStream.close();
                }

            } catch (IOException e) {
                log.error("excel文件導出失敗, 失敗緣由:{}", e);
            }
        }

    }

    /**
      * @Description 導出方法,生成excle
      * @author lst
      * @date 2020-4-28 11:39
      * @param filePath 絕對路徑, 如:/home/chenmingjian/Downloads/aaa.xlsx
      * @param data 數據源
     */
    public static void writeWithTemplate(String filePath, List<? extends BaseRowModel> data){
        writeWithTemplateAndSheet(filePath,data,null);
    }

    /**
      * @Description  導出方法,生成excle
      * @author lst
      * @date 2020-4-28 11:40
      * @param filePath 絕對路徑, 如:/home/chenmingjian/Downloads/aaa.xlsx
      * @param data 數據源
      * @param sheet excle頁面樣式
     */
    public static void writeWithTemplateAndSheet(String filePath, List<? extends BaseRowModel> data, Sheet sheet){
        if(CollectionUtils.isEmpty(data)){
            return;
        }
        //判斷是否使用默認的sheet
        sheet = (sheet != null) ? sheet : initSheet;
        sheet.setClazz(data.get(0).getClass());

        OutputStream outputStream = null;
        ExcelWriter writer = null;
        try {
            outputStream = new FileOutputStream(filePath);
            writer = EasyExcelFactory.getWriter(outputStream);
            writer.write(data,sheet);
        } catch (FileNotFoundException e) {
            log.error("找不到文件或文件路徑錯誤, 文件:{}", filePath);
        }finally {
            try {
                if(writer != null){
                    writer.finish();
                }

                if(outputStream != null){
                    outputStream.close();
                }
            } catch (IOException e) {
                log.error("excel文件導出失敗, 失敗緣由:{}", e);
            }
        }

    }

    /**
      * @Description  導出方法,生成多Sheet的excle
      * @author lst
      * @date 2020-4-28 11:41
      * @param filePath 絕對路徑, 如:/home/chenmingjian/Downloads/aaa.xlsx
      * @param multipleSheelPropetys
      * @return
     */
    public static void writeWithMultipleSheel(String filePath,List<MultipleSheelPropety> multipleSheelPropetys){
        if(CollectionUtils.isEmpty(multipleSheelPropetys)){
            return;
        }

        OutputStream outputStream = null;
        ExcelWriter writer = null;
        try {
            outputStream = new FileOutputStream(filePath);
            writer = EasyExcelFactory.getWriter(outputStream);
            for (MultipleSheelPropety multipleSheelPropety : multipleSheelPropetys) {
                Sheet sheet = multipleSheelPropety.getSheet() != null ? multipleSheelPropety.getSheet() : initSheet;
                if(!CollectionUtils.isEmpty(multipleSheelPropety.getData())){
                    sheet.setClazz(multipleSheelPropety.getData().get(0).getClass());
                }
                writer.write(multipleSheelPropety.getData(), sheet);
            }

        } catch (FileNotFoundException e) {
            log.error("找不到文件或文件路徑錯誤, 文件:{}", filePath);
        }finally {
            try {
                if(writer != null){
                    writer.finish();
                }

                if(outputStream != null){
                    outputStream.close();
                }
            } catch (IOException e) {
                log.error("excel文件導出失敗, 失敗緣由:{}", e);
            }
        }

    }


    /*********************匿名內部類開始,能夠提取出去******************************/

    @Data
    public static class MultipleSheelPropety{
        private List<? extends BaseRowModel> data;
        private Sheet sheet;
    }


    /**
      * 解析監聽器,讀取大於一千行的數據須要傳入次參數
      * 每解析一行會回調invoke()方法。
      * 整個excel解析結束會執行doAfterAllAnalysed()方法
      * @author lst
      * @date 2020-4-28 11:42
     */
    @Data
    public static class ExcelListenerPlus  extends AnalysisEventListener {

        private List<Object> datas = new ArrayList<>();

        /**
          * @Description  逐行解析
          * @author lst
          * @date 2020-4-28 11:43
          * @param object  當前行的數據
          * @param context
         */
        @Override
        public void invoke(Object object, AnalysisContext context) {
            //當前行
            // context.getCurrentRowNum()
            if (object != null) {
                datas.add(object);
            }
        }

        /**
          * @Description 解析完全部數據後會調用該方法
          * @author lst
          * @date 2020-4-28 11:43
          * @param context
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            //解析結束銷燬不用的資源
        }
    }

    public static class ExcelListener extends AnalysisEventListener<UserExcelProperty> {
        private List<UserExcelProperty> datas = new ArrayList<>();
        private static final int BATCH_COUNT = 1;

        @Override
        public void invoke(UserExcelProperty userExcelProperty, AnalysisContext analysisContext) {
            //數據存儲到datas,供批量處理,或後續本身業務邏輯處理。
            datas.add(userExcelProperty);
            //達到BATCH_COUNT了,須要去存儲一次數據庫,防止數據幾萬條數據在內存,容易OOM
            if(datas.size() >= BATCH_COUNT){
                data();
                // 存儲完成清理datas
                datas.clear();
            }
        }

        private void data() {
            for(UserExcelProperty userExcelProperty : datas){
                log.info("姓名:{},性別:{},年齡:{}",userExcelProperty.getName(),userExcelProperty.getSex(),userExcelProperty.getAge());
            }
        }

        /**
         * 全部數據解析完成了 都會來調用
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {

        }
    }
}

三、UserExcelProperty實體類

package com.example.mybaties.entity;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;
import lombok.Data;

/**
 * @DESCRIPTION UserExcelProperty實體類
 * @Author lst
 * @Date 2020-04-28 10:30
 */
@Data
public class UserExcelProperty extends BaseRowModel {

    @ExcelProperty(value = "姓名",index = 0)
    private String name;

    @ExcelProperty(value = "性別",index = 1)
    private String sex;

    @ExcelProperty(value = "年齡",index = 2)
    private Integer age;
}

四、UserController控制層(測試導入和導出的接口)

package com.example.mybaties.controller;

import com.example.mybaties.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;


/**
 * @author LST
 * @version 1.0
 * @Description: 用戶管理
 * @date 2019-12-28 16:22
 */
@RestController
@RequestMapping("/user")
@Api(value = "UserController", tags = "用戶接口")
public class UserController {

    @Autowired
    private UserService userService;

    
    /**
     * @Description: excel導出
     * @author: lst
     * @date: 2020-4-28 11:00
     */
    @GetMapping(value = "/user-export", produces = "application/json; charset=utf-8")
    public void  userExport() {
        userService.userExport();
    }

    /**
     * @Description 將用戶信息導出下載
     * @author lst
     * @date 2020-4-28 14:54
     * @param response
     */
    @GetMapping(value = "/download-export", produces = "application/json; charset=utf-8")
    public void downLoadExcel(HttpServletResponse response){
        userService.downLoadExcel(response);
    }

    /**
     * @Description  Excel文件用戶信息導入
     * @author lst
     * @date 2020-4-28 15:18
     * @param file 文件流
     */
    @PostMapping(value = "/user-import", produces = "application/json; charset=utf-8")
    @ApiOperation(value = "Excel文件用戶信息導入", notes = "Excel文件用戶信息導入", produces = "application/json")
    public void  userImport(@RequestParam(value = "file") MultipartFile file) {
        userService.userImport(file);
    }

}

五、UserService接口和UserServiceImpl具體實現代碼

package com.example.mybaties.service;

import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;

/**
 * @author lst
 * @version 1.0
 * @Description: 用戶表
 * @date 2019年04月09日 18:23
 */
public interface UserService {

    void userExport();

    void downLoadExcel(HttpServletResponse response);

    void userImport(MultipartFile file);

}
package com.example.mybaties.service.impl;

import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.example.mybaties.entity.UserExcelProperty;
import com.example.mybaties.service.UserService;
import com.example.mybaties.utils.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author lst
 * @version 1.0
 * @Description: 用戶表實現層
 * @date 2019年04月09日 18:23
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    /**
      * @Description: excel導出
      * @author: lst
      * @date: 2020-4-28 11:00
     */
    @Override
    public void userExport() {
        Long  startTime  = System.currentTimeMillis();
        //sheetNo: sheet頁碼,默認爲1
        //headLineMun: 從第幾行開始讀取數據,默認爲0, 表示從第一行開始讀取
        Sheet sheet = new Sheet(1,0, UserExcelProperty.class,"用戶信息",null);
        sheet.setAutoWidth(Boolean.TRUE);
        //設置列寬 設置每列的寬度
		/*Map columnWidth = new HashMap();
		columnWidth.put(0,10000);
        columnWidth.put(1,40000);columnWidth.put(2,10000);columnWidth.put(3,10000);
		sheet1.setColumnWidthMap(columnWidth);
		sheet1.setHead(createTestListStringHead2());*/

        List<UserExcelProperty> data = new ArrayList<>();
        for(int i = 0; i < 2000; i++){
            UserExcelProperty userExcelProperty = new UserExcelProperty();
            userExcelProperty.setAge(i);
            userExcelProperty.setName("測試"+i);
            userExcelProperty.setSex("1");
            data.add(userExcelProperty);
        }
        ExcelUtil.writeWithTemplateAndSheet("D:\\test.xls",data,sheet);

        Long  endTime  = System.currentTimeMillis();
        log.info("消耗時間:{}ms",endTime-startTime);

    }

    /**
      * @Description 將用戶信息導出下載
      * @author lst
      * @date 2020-4-28 14:54
      * @param response
     */
    @Override
    public void downLoadExcel(HttpServletResponse response) {
        Long  startTime  = System.currentTimeMillis();
        ExcelWriter writer = null;
        // 文件輸出位置
        OutputStream out = null;
        try {
            out = response.getOutputStream();
            writer = new ExcelWriter(response.getOutputStream(), ExcelTypeEnum.XLSX, true);
            Sheet sheet = new Sheet(1,0, UserExcelProperty.class,"用戶信息",null);
            sheet.setAutoWidth(Boolean.TRUE);
            //獲得要填充的數據
            List<UserExcelProperty> data = new ArrayList();
            for(int i = 0; i < 2000; i++){
                UserExcelProperty userExcelProperty = new UserExcelProperty();
                userExcelProperty.setAge(i);
                userExcelProperty.setName("測試"+i);
                userExcelProperty.setSex("1");
                data.add(userExcelProperty);
            }
            writer.write(data, sheet);
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + new String(("用戶信息.xls").getBytes(), "ISO8859-1"));
            out.flush();
        } catch (IOException e) {
            log.info("錯誤信息:{}",e.getMessage());
        }finally {
            writer.finish();
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        Long  endTime  = System.currentTimeMillis();
        log.info("消耗時間:{}ms",endTime-startTime);

    }

    /**
      * @Description  Excel文件用戶信息導入
      * @author lst
      * @date 2020-4-28 15:18
      * @param file 文件流
     */
    @Override
    public void userImport(MultipartFile file) {
        Long  startTime  = System.currentTimeMillis();
        if(!file.isEmpty()){
            try {
                InputStream inputStream = new BufferedInputStream(file.getInputStream());
                //實例化實現了AnalysisEventListener接口的類
                ExcelUtil.ExcelListener excelListener = new ExcelUtil.ExcelListener();
                ExcelReader reader = new ExcelReader(inputStream,null,excelListener);
                //讀取信息
                reader.read(new Sheet(1,1,UserExcelProperty.class));
            } catch (IOException e) {
                log.info("錯誤信息:{}",e.getMessage());
            }
        }
        Long  endTime  = System.currentTimeMillis();
        log.info("消耗時間:{}ms",endTime-startTime);
    }
}

六、生成Excel數據接口測試(http://127.0.0.1:8001/user/user-export)

使用Swagger測試以下圖:git

查看生成的Excel文件信息以下github

七、導出Excel數據下載接口測試(http://127.0.0.1:8001/user/download-export)

使用Swagger測試以下圖:web

Excel文件數據就是和上個接口同樣。spring

八、導入Excel數據接口測試(http://127.0.0.1:8001/user/user-import)

此接口我只作了打印,如須要插入數據能夠在ExcelListener類中去實現數據庫

使用Swagger測試以下圖:json

後臺打印了讀取Excel的數據,如圖app

動態表頭的導入和導出以項目實際需求進行開發。ide

相關文章
相關標籤/搜索