阿里巴巴EasyExcel轻松使用(上篇)

一、业务背景

​       项目组维护系统接到一个excel导出的功能,要求导出模板带有用户基础信息,管理员将其他信息补充完整后再次导入到系统当中,然后在列表展示相关信息。初次接到需求后想到使用Apache POI、jxl等框架来实现,但是考虑到表结构比较复杂,用这些框架会写大量的代码,再者,我们的业务系统数据量非常庞大,他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。那有没有一种开源的、使用轻便的、且不会OOM的excel处理工具呢?,没错,就是EasyExcel!
​      EasyExcel是阿里巴巴开源的一款excel处理框架,它重写了poi对07版Excel的解析,一个3M的excel用POI sax解析需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便

二、使用过程

2.1 jar包引入

​       使用EasyExcel只需引入一个jar包就可以,非常简单。EasyExcel导出有很多方式,这里我们讲解常用的web直接导出和web模板填充导出。这里的jar包版本请根据自己需要选择合适的版本,一般选择最新版本。

 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>x.x.x</version>
    </dependency>
2.2 实体类

 ​      这里的实体类是与excel表头字段是一一对应的,通过@ExcelProperty(value = “”, index = ) 和excel表头进行关联,value为excel表头字段,index 为excel表头字段下标位置,当存在相同的表头内容,就可以使用index下标进行区分。以下为示例代码:

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

import java.time.LocalDateTime;

@Data
public class DemoDate {
    @ExcelProperty(value = "字符串标题",index = 0)
    private String string;
    @ExcelProperty(value = "日期标题",index = 1)
    private LocalDateTime localDateTime;
    @ExcelProperty(value = "数字标题",index = 2)
    private double doubleValue;
}

​ excel文件示例:
在这里插入图片描述

如果存在复杂表头,也可以使用 @ExcelProperty(value = “xxx”,index = xxx)来完成,复杂表头excel示例:
在这里插入图片描述
对应的实体写法:

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

import java.time.LocalDateTime;

@Data
public class DemoDate {
    @ExcelProperty(value = {"字符串","字符串标题"},index = 0)
    private String string;
    @ExcelProperty(value = {"其他","日期标题"},index = 1)
    private LocalDateTime localDateTime;
    @ExcelProperty(value = {"其他","数字标题"},index = 2)
    private double doubleValue;
}
2.3 直接导出

 ​      直接导出就是不使用提供的模板导出数据到excel,优点是直接导出表头信息灵活可变,可根据实体里面的字段控制导出表头信息。缺点也非常明显,这种导出方式excel样式、公式等信息需要自己用代码去实现,非常繁琐。以下是直接导出的Java代码:

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;


@RestController
@RequestMapping("/incomeExcel")
@Slf4j
public class BasicFamilyIncomeExcelController {
    @Resource
    private XXXExcelService XXXExcelService;

    /**
     * 下载模版
     *
     */
    @GetMapping("/download")
    public void exportExcel(HttpServletResponse response) throws Exception {
        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode("xxxx", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            // 这里需要设置不关闭流
            try (ExcelWriter excelWriter =
EasyExcel.write(response.getOutputStream(),DemoData.class).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build()) {
                WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
                // 查询导出总条数
                int totalData = XXXExcelService.getExportCounts();
                //分页查询,每次查询100条
                int totalPages = (int) Math.ceil((double) totalData / 50);
                for (int i = 1; i <= totalPages; i++) {
                    int pageNO = i;
                    int pageSize = 100;
                    List<DemoData> datas = XXXExcelService.selectXXXlist(pageNO,pageSize);
                    excelWriter.fill(datas, fillConfig, writeSheet);
                }
            }
        } catch (Exception e) {
            log.error("下载模板文件失败", e);
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = MapUtils.newHashMap();
            map.put("status", "failure");
            map.put("message", "下载模板文件失败" + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
        }
    }
}

       这里是写回浏览器的代码部分,固定的格式,fileName为导出模板的名称,使用了UTF8编码,避免了乱码。

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("xxxx", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

       这块代码是实现文件导出的核心功能,首先使用 try() 将流包起来,这样就不用去主动关闭了。DemoData.class为实体类,里面的字段一一对应excel头部字段。excelType(ExcelTypeEnum.XLSX)为导出excel的后缀类型。为了避免一次性查询出太多数据量造成OOM,这里使用了分页查询,每次分页查询出的数据分批量写入excel从而减轻内存压力。

// 这里需要设置不关闭流
            try (ExcelWriter excelWriter =
EasyExcel.write(response.getOutputStream(),DemoData.class).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build()) {
                WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
                // 查询导出总条数
                int totalData = XXXExcelService.getExportCounts();
                //分页查询,每次查询100条
                int totalPages = (int) Math.ceil((double) totalData / 50);
                for (int i = 1; i <= totalPages; i++) {
                    int pageNO = i;
                    int pageSize = 100;
                    List<DemoData> datas = XXXExcelService.selectXXXlist(pageNO,pageSize);
                    excelWriter.write(datas, writeSheet);
                }
            }
2.3 模板填充导出

       所谓模板填充导出就是自己原先将excel模板写好,然后将查出数据直接填充到模板excel对应的列当中就行了,这样做的好处就是可以不用去设置模板excel样式,公式等内容,直接把数据灌入就行。缺点就是不够灵活,只能按照预先设定好的模板进行导出。但是一般情况下,系统业务稳定后,模板也不会发生变化。故推荐这种导出方式。

下面是模板excel导出的代码:

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;


@RestController
@RequestMapping("/incomeExcel")
@Slf4j
public class BasicFamilyIncomeExcelController {
    @Resource
    private XXXExcelService XXXExcelService;

    /**
     * 下载模版
     *
     */
    @GetMapping("/download")
    public void exportExcel(HttpServletResponse response) throws Exception {
        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode("xxxx", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            //获取模版
            ClassPathResource classPathResource = new ClassPathResource("/template/xxx.xlsx");
            InputStream templateInputStream = classPathResource.getInputStream();
            // 这里需要设置不关闭流
            try (ExcelWriter excelWriter =
EasyExcel.write(response.getOutputStream()).withTemplate(templateInputStream).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build()) {
                //配置多组数据填充完后,需要换行,防止覆盖模板中的单组数据模板
                FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
                WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
                // 查询导出总条数
                int totalData = XXXExcelService.getExportCounts();
                //分页查询,每次查询100条
                int totalPages = (int) Math.ceil((double) totalData / 50);
                for (int i = 1; i <= totalPages; i++) {
                    int pageNO = i;
                    int pageSize = 100;
                    List<DemoData> datas = XXXExcelService.selectXXXlist(pageNO,pageSize);
                    excelWriter.fill(datas, fillConfig, writeSheet);
                }
            }
        } catch (Exception e) {
            log.error("下载模板文件失败", e);
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = MapUtils.newHashMap();
            map.put("status", "failure");
            map.put("message", "下载模板文件失败" + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
        }
    }
}

       这里是获取模板流的过程:

	ClassPathResource classPathResource = new ClassPathResource("/template/xxx.xlsx");
	InputStream templateInputStream = classPathResource.getInputStream();

       这里是填充模板的过程,和直接导出不同的是,创建excelWriter 多了.withTemplate(templateInputStream)这句话,最后填充模板的方法使用fill(datas, fillConfig, writeSheet)来完成,而直接导出使用 excelWriter.write(datas, writeSheet);fillConfig用来填充数据的时候,如果模版里面有事先写好的数据,为了避免模版填充的时候覆盖原来的数据,故使用fillConfig,每次填充完,写有数据的一行会往下走一行。

             // 这里需要设置不关闭流
            try (ExcelWriter excelWriter =
EasyExcel.write(response.getOutputStream()).withTemplate(templateInputStream).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build()) {
                //配置多组数据填充完后,需要换行,防止覆盖模板中的单组数据模板
                FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
                WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
                // 查询导出总条数
                int totalData = XXXExcelService.getExportCounts();
                //分页查询,每次查询100条
                int totalPages = (int) Math.ceil((double) totalData / 50);
                for (int i = 1; i <= totalPages; i++) {
                    int pageNO = i;
                    int pageSize = 100;
                    List<DemoData> datas = XXXExcelService.selectXXXlist(pageNO,pageSize);
                    excelWriter.fill(datas, fillConfig, writeSheet);
                }
            }

       填充模版示例。如下图所示,在对应的表头下面用{.xxx}方式填充模板,xxx是实体对应的字段,这种方式适用于返回数据是集合的形式。如果返回的是单个实体数据,那么使用{xxx}的方式,和前面的区别就是少了一个 .
在这里插入图片描述

三、使用总结

       EasyExcel是一款简单易用的excel处理框架,有着非常优秀的数据流处理能力,它帮我屏蔽了底层处理excel的复杂API,让excel处理变的高效。我出的这篇教程希望能帮助到大家,后面我将出EasyExcel的导入功能,敬请期待。。。

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

屿丶斐然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值