jjzjj

5、基于EasyExcel的导入导出

爱文(Iven) 2023-04-16 原文

一、Apach POI处理Excel的方式:

传统Excel操作或者解析都是利用Apach POI进行操作,POI中处理Excel有以下几种方式:

1、HSSFWorkbook:

HSSFWorkbook用来处理.xls后缀的Excel,即适用于Excel2003以前(包括2003)的版本。因为其最大只能处理65535行的数据,所以现在已经很少使用了

2、XSSFWorkbook:

XSSFWorkbook是现在处理Excel比较常见的方式。其适用于.xlsx后缀的Excel,即Excel2007后的版本。能够最多处理104万行数据。但是其在读取/处理Excel时会一口气将Excel内容写入到内存,因此在处理的Excel文件较大时可能打爆内存,造成OOM异常(内存溢出)

3、SXSSFWorkbook:

SXSSFWorkbook相当于是XSSFWorkbook的改良版本,在初始化SXSSFWorkbook实例时,需要填写一个缓冲行数参数(默认100行),当读入到内存中的数据超过该数值后,会像队列一样将最前面的数据保存到硬盘中,从而避免出现OOM。这么一看该方式简直完美啊,不过因为超过缓存行的数据都写到硬盘中了,所以如果你想要获取这块的内容(比如复制这块内容到另一个Excel中)就会发现取不到了,因为不在内存中,所以无法通过SXSSFWorkbook实例获取该部分内容。

 

二、Apach POI框架的不足:

1、使用步骤繁琐;

2、动态写出Excel操作非常麻烦;

3、对于新手来说,很难在短时间内上手;

4、读写时需要占用较大的内容,当数据量大时容器发生OOM;

基于上述原因,阿里开源出一款易上手,且比较节省内存的Excel操作框架:EasyExcel

 

三、Apach POI、EasyPoi与EasyExcel的区别:

1、POI 优点在于自由,但是迎来的就是复杂度,和大数据量时候性能的缺点

2、EasyPoi基于POI 的二次封装,解决了大部分的常用场景,简化了代码,但是特别复杂表格处理还是不行,而且性能的话和poi差不多,简单来说就是简化了Poi的操作,少些点代码,总体来说,easypoi和easyexcel都是基于apache poi进行二次开发的。

3、easypoi和easyexcel的不同点在于:

(1)、easypoi 在读写数据的时候,优先是先将数据写入内存,优点是读写性能非常高,但是当数据量很大的时候,会出现oom,当然它也提供了 sax 模式的读写方式,需要调用特定的方法实现。

(2)、easyexcel 基于sax模式进行读写数据,不会出现oom情况,程序有过高并发场景的验证,因此程序运行比较稳定,相对于 easypoi 来说,读写性能稍慢!

(3)、easypoi 与 easyexcel 还有一点区别在于,easypoi 对定制化的导出支持非常的丰富,如果当前的项目需求,并发量不大、数据量也不大,但是需要导出 excel 的文件样式千差万别,那么我推荐你用 easypoi;反之,使用 easyexcel !

 

四、EasyExcel的使用:

4.1、操作流程:

建工程——》改POM——》写YML——》业务类

4.1.1、添加依赖:

        <!-- easyexcel 依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.7</version>
        </dependency>    

4.1.2、写YML:

server:
  port: 8081

spring:
  application:
    name: demo    #项目名
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource    #当前数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver    #mysql驱动包
    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    druid:
      test-while-idle: false  #关闭空闲检测

mybatis:
  mapperLocations: classpath:mapper/*.xml    #resource目录下建mapper包,存放xml文件

 

4.2、EasyExcel的导出操作(单个与批量导出):

4.2.1、通用导出工具类ExportUtil:

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.util.List;

@Slf4j
public class ExportUtil {
    /**
     * 导出 Excel
     *
     * @param response  :响应
     * @param data      :导出的list集合
     * @param fileName  :Excel名(最好英文,无需后缀)
     * @param sheetName :sheet页名
     * @param clazz     :导出Excel实体类
     * @throws Exception
     */
    public static void writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName, String sheetName, Class clazz) throws Exception {
        //表头样式
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        //设置表头居中对齐
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //内容样式
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        //设置内容靠左对齐
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);

        EasyExcel.write(getOutputStream(fileName, response), clazz)
                .excelType(ExcelTypeEnum.XLSX)//读取的文件类型
                .sheet(sheetName)//读取的sheet,可以是行号也可以是sheet名
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())//设置自动适应宽度
                .registerWriteHandler(horizontalCellStyleStrategy)//设置样式(或:registerWriteHandler(createStyleStrategy))
                .doWrite(data);
    }

    /**
     * 格式处理
     */
    private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setCharacterEncoding("utf8");
        response.addHeader("Content-disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue());
        return response.getOutputStream();
    }

    /**
     * 自定义样式
     */
    private static HorizontalCellStyleStrategy createStyleStrategy() {
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为红色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 10);
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);  //底边框
        contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);  //左边框
        contentWriteCellStyle.setBorderRight(BorderStyle.THIN);  //右边框
        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);  //顶边框
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short) 10);
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        // 执行策略
        HorizontalCellStyleStrategy horizontalCellStyleStrategy =
                new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);

        return horizontalCellStyleStrategy;
    }
}

4.2.2、业务流程:

1、请求体:

import lombok.Data;

import java.util.List;

@Data
public class RequestVO {
    private String id;
    private List<String> ids;
    private Boolean way;
}

2、控制类:

    /**
     * 导出
     * 1、单个导出
     * 2、批量导出
     *
     * http://localhost:8081/v1/export-data
     * {
     *     "id":"xxx",
     *     "ids":["xxx","xxx"],
     *     "way":false
     * }
     * */
    @PostMapping("/export-data")
    public void exportList(
            @RequestBody RequestVO requestVO,
            HttpServletResponse response) {
        //导出方式:单个/批量
        if(true == requestVO.getWay()){
            requestVO.setId(null);
        }
        operateService.exportData(requestVO,response);
    }

3、业务类:

    @Autowired
    private OperateMapper operateMapper;

    /**
     * 导出操作
     */
    @Override
    public void exportData(RequestVO requestVO, HttpServletResponse response) {
        //mysql查询
        List<BasicDemo> list = operateMapper.selectAllDemo(requestVO);
        //转换
        List<ExportExcelVO> exportList = new ArrayList<>();
        Optional.ofNullable(list).ifPresent(po -> {
            po.stream().forEach(p -> {
                ExportExcelVO exportExcelVO = ExportExcelVO.builder()
                        .uuid(p.getUuid())
                        .name(p.getName())
                        .status(p.getStatus())
                        .startTime(p.getStartTime())
                        .build();
                exportList.add(exportExcelVO);
            });
        });

        //导出
        try {
            writeExcel(response, exportList, "result", "表名", ExportExcelVO.class);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("错误信息Exception:", e);
        }
        log.info("执行完毕,导出成功");
    }

4、导出实体类:

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 com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

@HeadRowHeight(value = 35) // 设置表头行高
@ContentRowHeight(value = 25) // 设置内容行高
//@ColumnWidth(value = 50) // 设置列宽
@Data
@Builder
public class ExportExcelVO implements Serializable {

    @ExcelProperty(value = {"编号"},order = 1)
    private String uuid;

    @ExcelProperty(value = {"基本信息","详情","名字"},order = 2)
    private String name;

    @ExcelProperty(value = {"基本信息","详情","状态"},order = 3)
    private String status;

    @ExcelProperty(value = {"基本信息","操作时间","时间"},order = 4)
    @DateTimeFormat("yyyy-MM-dd")   //时间格式
    private String startTime;


}

5、SQL语句:

    <select id="selectAllDemo" resultMap="BaseResultMap" parameterType="java.lang.String">
        select
        <include refid="Base_Column_List"/>
        from page_demo
        where 1=1
        <if test="id neq null and id != ''">
            AND uuid = #{id}
        </if>
        <if test="ids != null and ids.size()!=0">
            AND uuid in
            <foreach collection="ids" item="item" index="index" open="(" separator=","
                     close=")">
                #{item}
            </foreach>
        </if>
    </select>

6、导出样本:

 

4.3、EasyExcel的导入操作(模板下载与数据导入):

4.3.1、通用导入工具类ImportUtil:

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;


@Slf4j
public class ImportUtil {
    /**
     * 导入 Excel
     *
     * @param multipartFile excel文件
     * @param clazz         数据类型的class对象
     * @param <T>
     * @return
     */
    public static <T> List<T> importExcel(MultipartFile multipartFile, Class<T> clazz) throws Exception {
        List<T> list = new ArrayList<>();
        InputStream inputStream = multipartFile.getInputStream();

        EasyExcel.read(inputStream, clazz, new AnalysisEventListener<T>() {
            //每解析一行就会调用一次,data数据表示解析出来一行的数据
            @Override
            public void invoke(T data, AnalysisContext context) {
                list.add(data);
            }

            //当全部数据读取完成后调用该方法
            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                log.info("解析完成");
            }
        }).sheet().doRead();
        return list;
    }
}

4.3.2、业务流程:

1、控制类:

    @Autowired
    private OperateService operateService;

    /**
     * 导入模板下载
     * http://localhost:8081/v1/download
     * */
    @GetMapping("/download")
    public void downloadTemplate(HttpServletResponse response) {
        operateService.importTemplate(response);
    }

    /**
     * 导入
     * http://localhost:8081/v1/import-data
     */
    @PostMapping(value="/import-data")
    public Response<List<ImportExcelVO>> importExcel(@RequestParam(value = "file") MultipartFile file)  {
        List<ImportExcelVO> list = operateService.importData(file);
        return Response.success(list);
    }

2、业务类:

    /**
     * 导入模板下载
     */
    @Override
    public void importTemplate(HttpServletResponse response) {
        List<ImportExcelVO> importExcelVO = new ArrayList<>();
        try {
            //相当导出 Excel操作
            writeExcel(response, importExcelVO, "importTemplate", "表名", ImportExcelVO.class);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("错误信息Exception:", e);
        }
        log.info("执行完毕,导出成功");
    }

    /**
     * 导入操作
     */
    @Override
    public List<ImportExcelVO> importData(MultipartFile file) {
        List<ImportExcelVO> list = new ArrayList<>();
        try {
            //获取数据
            list = importExcel(file, ImportExcelVO.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //数据操作:
        for (ImportExcelVO importExcelVO :
                list) {
            log.info("输出:" + importExcelVO);
        }
        return list;
    }

4、导入实体类:

@HeadRowHeight(value = 35) // 设置表头行高
@ContentRowHeight(value = 25) // 设置内容行高
//@ColumnWidth(value = 50) // 设置列宽
@Data
public class ImportExcelVO implements Serializable {

    @ExcelProperty(value = {"编号"},order = 1)
    private String uuid;

    @ExcelProperty(value = {"基本信息","详情","名字"},order = 2)
    private String name;

    @ExcelProperty(value = {"基本信息","详情","状态"},order = 3)
    private String status;

    @ExcelProperty(value = {"基本信息","操作时间","时间"},order = 4)
    @DateTimeFormat("yyyy-MM-dd")   //时间格式
    private String startTime;


}

 

五、参考:

参考

 

有关5、基于EasyExcel的导入导出的更多相关文章

  1. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  2. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  5. ruby-on-rails - (Ruby,Rails) 基于角色的身份验证和用户管理...? - 2

    我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源

  6. Ruby:如何从另一个文件导入变量? - 2

    我正在尝试创建一个与compass一起使用的本地配置文件,这样我们就可以处理开发人员机器上的不同导入路径。到目前为止,我已经尝试将文件导入到异常block中,以防它不存在,然后进一步使用该变量:local_config.rbVENV_FOLDER='venv'config.rbVENV_FOLDER='.'beginrequire'local_config.rb'rescueLoadErrorendputsVENV_FOLDER通常我是一名Python开发人员,所以我希望导入将VENV_FOLDER的值更改为venv,但它仍然是。之后。有没有一种方法可以导入local_config.r

  7. ruby - 在 Rakefile 中动态生成 Rake 测试任务(基于现有的测试文件) - 2

    我正在根据Rakefile中的现有测试文件动态生成测试任务。假设您有各种以模式命名的单元测试文件test_.rb.所以我正在做的是创建一个以“测试”命名空间内的文件名命名的任务。使用下面的代码,我可以用raketest:调用所有测试require'rake/testtask'task:default=>'test:all'namespace:testdodesc"Runalltests"Rake::TestTask.new(:all)do|t|t.test_files=FileList['test_*.rb']endFileList['test_*.rb'].eachdo|task|n

  8. ruby - Ruby 导入的方法总是私有(private)的吗? - 2

    最好用一个例子来解释:文件1.rb:deffooputs123end文件2.rb:classArequire'file1'endA.new.foo将给出错误“':调用了私有(private)方法'foo'”。我可以通过执行A.new.send("foo")来解决这个问题,但是有没有办法公开导入的方法?编辑:澄清一下,我没有混淆include和require。另外,我不能使用正常包含的原因(正如许多人正确指出的那样)是因为这是元编程设置的一部分。我需要允许用户在运行时添加功能;例如,他可以说“run-this-app--includefile1.rb”,应用程序的行为将根据他在file1

  9. ruby - 如何使用 Ruby 基于字母数字字符串生成颜色? - 2

    我想要像“嘿那里”这样的东西变成,例如,#316583。我希望将任意长度的字符串“归结”为十六进制颜色。我不知道从哪里开始。我在想,每个字符串的MD5散列都是不同的-但如何将该散列转换为十六进制颜色数字? 最佳答案 你可以只取几位前几位:require'digest/md5'color=Digest::MD5.hexdigest('Mytext')[0..5] 关于ruby-如何使用Ruby基于字母数字字符串生成颜色?,我们在StackOverflow上找到一个类似的问题:

  10. ruby-on-rails - Rails 导入 CSV 错误 : invalid byte sequence in UTF-8 - 2

    尝试在我的Rails应用程序中导入CSV文件时,出现错误UTF-8中的无效字节序列。一切正常,直到我添加了一个gsub方法来将其中一个CSV列与我的数据库中的一个字段进行比较。当我导入CSV文件时,我想检查每一行的地址是否包含在特定客户端的不同地址数组中。我有一个带有alt_addresses属性的客户端模型,其中包含客户端地址的几种不同可能格式。然后我有一个引用模型(如果您熟悉本地SEO,您就会知道这个术语)。引用模型没有地址字段,但它有一个nap_correct?字段(NAP代表“姓名”、“地址”、“电话号码”)。如果CSV行的名称、地址和电话号码与我在该客户的数据库中拥有的相同,

随机推荐