风萧萧兮易水寒

Excel导出导入

1. EasyExcel介绍

1.1 为什么使用EasyExcel?

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便

快速、简单避免OOM的java处理Excel工具,64M内存1分钟内读取75M(46W行25列)的Excel。

写的内容大致和阅读官方文档差不多

2. 开始使用

2.1 引入jar

<!--easyexcel插件 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.4</version>
</dependency>
<!--lombok插件-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2.2 实体类

根据自己的情况自定义表格导入导出实体类

package top.gmaya.demo.excelentity;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;

import java.util.Date;

/**
 * @ Description   :  用户表格专用实体
 * @ Author        :  GMaya
 * @ CreateDate    :  2020/1/16 9:13
 * @ Version       :  1.0
 */
@Data
public class UserExcel{
    /** 生成报表时忽略,不生成次字段 */
    @ExcelIgnore
    private Long id;
    /** 定义表头名称和位置,0代表第一列 */
    @ExcelProperty(value = "用户名称",index = 0)
    private String userName;
    @ExcelProperty(value = "用户年龄",index = 1)
    private int userAge;
    @ExcelProperty(value = "用户昵称",index = 2)
    private String userNick;
    @ExcelProperty(value = "用户地址",index = 3)
    private String userAddress;
    /** 指定列宽 */
    @ColumnWidth(20)
    /** 转化时间 */
    @DateTimeFormat(value = "yyyy-MM-dd")
    @ExcelProperty(value = "用户生日",index = 4)
    private Date userBrithday;
};

2.3 ExcelUtil工具类

根据实际情况封装成工具类,我就写了俩

package top.gmaya.demo.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;

/**
 * @ Description   :  excel工具类
 * @ Author        :  GMaya
 * @ CreateDate    :  2020/1/16 9:35
 * @ Version       :  1.0
 */
public class ExcelUtil {

    /**
     * 导出文件 到浏览器
     * @param response 响应请求
     * @param excelName excel名称
     * @param sheetName sheet页面名称
     * @param clazz 要转换的实体类类型
     * @param data 要导出的数据
     * @throws Exception 异常
     */
    public static void export2Web(HttpServletResponse response, String excelName, String sheetName, Class clazz, List data) throws Exception {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码
        excelName = URLEncoder.encode(excelName, "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + excelName + ExcelTypeEnum.XLSX.getValue());
        EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);
    };

    /**
     * 读取Excel表格
     * @param excel 文件
     * @param head 实体类映射
     * @param readListener 模板的读取类
     * @throws Exception
     */
    public static void readExcel(MultipartFile excel, Class head,ReadListener readListener) throws Exception{
        EasyExcel.read(excel.getInputStream(),head,readListener).sheet().doRead();
    };

};

2.4 模板读取类

创建模板读取类, 这个是表格导入的时候需要的,表格中的数据将在这个类里面单独处理,根据类里面设置的数值进行存库,也就是10w条数据,每当1000条就存一下表,方便内存回收。 不然一次性读10w,要占多少。。。
实际情况将下面的构造方法注释打开,相当于你在controlle中调用,然后将Service传进来就ok了。

package top.gmaya.demo.excelentity;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

/**
 * @ Description   :  模板的读取类
 * @ Author        :  GMaya
 * @ CreateDate    :  2020/1/16 10:43
 * @ Version       :  1.0
 */
// 有个很重要的点 UserListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j public class UserListener extends AnalysisEventListener<UserExcel> {

    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<UserExcel> list = new ArrayList<>();
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    //    private UserService userService;

   /* public UserListener(){
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        userService = new UserService();
    };*/

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param userService
     */
   /* public UserListener(UserService userService) {
        this.userService = userService;
    };*/

    /**
     * 这个每一条数据解析都会来调用
     * @param data
     * @param context
     *
     */
    @Override
    public void invoke(UserExcel data, AnalysisContext context) {
        log.info("解析到一条数据:{};", data.getUserName());
        list.add(data);
        if (list.size() >= BATCH_COUNT) {
            // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
            saveData();
            // 存储完成清理 list
            list.clear();
        };
    };

    @Override public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
        log.info("所有数据解析完成!");
    };

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{};条数据,开始存储数据库!", list.size());
        //        userService.saveBatch(list);
        log.info("存储数据库成功!");
    };
};

2.5 controller测试

package top.gmaya.demo.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import top.gmaya.demo.entity.User;
import top.gmaya.demo.excelentity.UserExcel;
import top.gmaya.demo.excelentity.UserListener;
import top.gmaya.demo.util.ExcelUtil;

import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @ Description   : 测试
 * @ Author        :  GMaya
 * @ CreateDate    :  2020/1/15 16:55
 * @ Version       :  1.0
 */
@Slf4j @RestController public class DemoController {

    /**
     * 导出excel
     * @param response
     */
    @GetMapping("/export2Web") public void export2Web(HttpServletResponse response, String id) {
        // 模拟数据
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User((long) i, "张三" + i, i, "小三" + i, "杭州" + i, new Date());
            list.add(user);
        };

        try {
            ExcelUtil.export2Web(response, "用户表", "用户信息", UserExcel.class, list);
        }; catch (Exception e) {
            log.error("报表导出异常:", e);
        };
    };

    /**
     * 导入excel
     * @param file
     * @return
     */
    @PostMapping("/exportImport") public String exportImport(MultipartFile file) {
        try {
            ExcelUtil.readExcel(file, UserExcel.class, new UserListener());
        }; catch (Exception e) {
            e.printStackTrace();
        };
        return "succeed";
    };

};

3. 测试

3.1 导出测试

启动项目,浏览器访问接口
直接弹框提示保存位置。保存查看里面内容
导出结果

3.2 导入测试

使用postman测试文件导入
导入结果
key:Content-Type
value:multipart/form-data
头部信息
结果

坚持原创技术分享,您的支持将鼓励我继续创作!
  • 本文作者: GMaya
  • 本文链接: https://gmaya.top/2020/0116/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!