admin管理员组

文章数量:1609530

前段时间项目上需要导入一个3000多条数据的Excel耗时十几分钟,使用的是传统的Apache poi的,最后统一决定用阿里的开源easy excel。
阿里在18年3月份左右发布easyexcel,刚发布就是打着“低内存”解决POI的oom的口号,本人在测试过程中发现相比poi,确实在使用的体验、操作简便性上有不少提升,感谢阿里团队的努力,官方给出的项目demp的github地址:https://github/alibaba/easyexcel

easyexcel核心功能

  • 读任意大小的03、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集成

1、pom依赖引入

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

2、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) {

        }
    }
}

3、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;
}

4、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);
    }

}

5、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);
    }
}

6、生成Excel数据接口测试(http://127.0.0.1:8001/user/user-export)

使用Swagger测试如下图:

查看生成的Excel文件信息如下

7、导出Excel数据下载接口测试(http://127.0.0.1:8001/user/download-export)

使用Swagger测试如下图:

Excel文件数据就是和上个接口一样。

8、导入Excel数据接口测试(http://127.0.0.1:8001/user/user-import)

此接口我只做了打印,如需要插入数据可以在ExcelListener类中去实现

使用Swagger测试如下图:

后台打印了读取Excel的数据,如图

动态表头的导入和导出以项目实际需求进行开发。

 

 

 

本文标签: 阿里开源入门操作简单