【EasyExcel导入、导出(百万数据量测试)粘贴即用】

需求:测试EasyExcel的导入导出使用、性能,测试数据量3个字段100万条数据;

测试环境:idea + maven + springboot + mybatis-plus + mysql + swagger

文章目录(目录跳转可能会不准确,建议直接Ctrl+F搜目录吧)

前言

一、项目整体目录

二、数据库

1.创建表

三、后端

1、pom.xml文件

2、yml文件(导入、导出共用代码)

3、共用的代码文件(使用其他框架的可以忽略①②③④⑤操作)

① 实体类对象TestExcel(导入、导出共用代码)

② mapper层(导入、导出共用代码)

③ service层(导入、导出共用代码)

④ impl实现层(导入、导出共用代码)

⑤ 分页工具(导入、导出共用代码)

⑥ EasyExcel的工具类(最主要的文件,如果使用的其它框架,可以忽略上面的代码,注释会尽量写的详细一些)

4、使用for循环创建数据,并导出成excel文件(controller的依赖全放在这里了,后面不再重复写)

5、 excel导入

6、excel导出

四、总结


前言

导入流程:用户上传文件-->后端获取文件流-->侦听器读取文件-->批量插入数据库
导出流程:用户点击按钮-->调用后端接口-->分页查询需要导出的数据-->导出成excel文件

因为是测试项目,为了更方便、明了,所以所有操作均放在了controller里,正式使用时注意放入接口实现层impl里,

如果是使用其它框架的,请忽略“后端目录下的①②③④⑤操作”,改用自己框架的方法即可

一、项目整体目录

二、数据库

1.创建表

CREATE TABLE test_excel (
  user_id varchar(255) NOT NULL COMMENT '表主键id',
  user_name varchar(255) NOT NULL COMMENT '用户姓名,不能为空',
  user_age varchar(255) DEFAULT NULL COMMENT '用户年龄,允许为空',
  user_cardid varchar(255) NOT NULL COMMENT '身份证号,不能为空,不允许重复',
  PRIMARY KEY (user_id)
);

三、后端

1、pom.xml文件

主要依赖是:

<dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>easyexcel</artifactId>
     <version>2.2.10</version>
</dependency>

全部依赖(根据自己的需求选择依赖,不用全部导入):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.bug</groupId>
    <artifactId>bug</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>my_springboot1</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ itextpdf依赖包 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.6</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ itextpdf依赖包 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
        <!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 用于测试依赖包 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
        <!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 用于测试依赖包 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
        <!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ slf4j+logback包 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ slf4j+logback包 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
        <!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ json包 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ json包 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
        <!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 过滤器 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 过滤器 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
        <!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ swagger ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ swagger ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
        <!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 连接数据库-MySQL-mybatis-plus ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <version>2.5.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 连接数据库-MySQL-mybatis-plus  ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
        <!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ Easy Excel ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.10</version>
        </dependency>
        <!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ Easy Excel ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
        <!-- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ lombok ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
        <!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ lombok ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.3</version>
            </plugin>
        </plugins>

        <resources>
            <resource>
                <directory>src/main/java</directory>
                <excludes>
                    <exclude>**/*.java</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>

    </build>

</project>

2、yml文件(导入、导出共用代码)

spring:
  devtools:
    restart:
      enabled: true  #设置开启热部署
      additional-paths: src/main/java #重启目录
      exclude: WEB-INF/**
  freemarker:
    cache: false    #页面不加载缓存,修改即时生效
  servlet:
    multipart: #这里必须要加这配置,不然一百万条数据文件太大会拒绝导入
      max-request-size: 100MB
      max-file-size: 100MB

  #数据库的配置,这里有个关键点,因为用的是mybatis-plus的saveBatch()方法批量插入数据库的,
  #所以这里必须加rewriteBatchedStatements=true,不然插入会非常的慢,
  #使用其它插入方式的可以忽略此配置
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://ip地址:端口号/数据库名称?rewriteBatchedStatements=true
    username: 用户名
    password: 密码

3、共用的代码文件(使用其他框架的可以忽略①②③④⑤操作

开始上代码咯

① 实体类对象TestExcel(导入、导出共用代码)

package com.bug.entity;

import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("test_excel")
public class TestExcel {

  /**
   * 可以通过index和name去匹配excel里的列
   * 注意name,index值不要重复
   */
  @TableId
  private String userId;

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

  @ExcelProperty(value = "年龄",index = 1)
  private String userAge;

  @ExcelProperty(value = "身份证号",index = 2)
  private String userCardid;


}

② mapper层(导入、导出共用代码)

package com.bug.mapper.excel;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bug.entity.TestExcel;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface TestExcelMapper extends BaseMapper<TestExcel> {
}

③ service层(导入、导出共用代码)

package com.bug.service.excel;

import com.baomidou.mybatisplus.extension.service.IService;
import com.bug.entity.TestExcel;

public interface TestExcelService extends IService<TestExcel> {

}

④ impl实现层(导入、导出共用代码)

package com.bug.service.excel.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bug.entity.TestExcel;
import com.bug.mapper.excel.TestExcelMapper;
import com.bug.service.excel.TestExcelService;
import org.springframework.stereotype.Service;

@Service
public class TestExcelServiceImpl extends ServiceImpl<TestExcelMapper, TestExcel> implements TestExcelService {

}

⑤ 分页工具(导入、导出共用代码)

package com.bug.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PageConfig {

    /**
     * 分页工具
     * @return PaginationInterceptor
     */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        return new PaginationInterceptor();
    }

}

⑥ EasyExcel的工具类(最主要的文件,如果使用的其它框架,可以忽略上面的代码,注释会尽量写的详细一些)

package com.bug.util.excel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.fastjson.JSON;
import com.bug.entity.TestExcel;
import com.bug.service.excel.TestExcelService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * 小bug
 * excel导入的工具类
 * 是不是很奇怪这里是实现的ReadListener接口而不是继承的AnalysisEventListener类
 * 因为源码里AnalysisEventListener类也是继承的ReadListener接口,何必多此一举呢
 */
public class ExcelUpload implements ReadListener<TestExcel> {

    private TestExcelService testExcelService;
    /**
     * 注意这里是不能交给spring管理的,就是不能使用@Resource,或者@Autowired注入
     * 可以使用构造方法的方式获取你想要的对象
     * @param testExcelService testExcelService
     */
    public ExcelUpload(TestExcelService testExcelService){
        this.testExcelService = testExcelService;
    }

    /**
     * 无参构造方法,不能省略
     */
    public ExcelUpload(){}

    public static final Logger log = LoggerFactory.getLogger(ExcelUpload.class);

    private static final int count = 10000;//设置读取的条数,每次达到指定条数时就保存入数据库
    private List<TestExcel> testExcelListTure = new ArrayList<>();//校验正确的数据集合,数量达到设定值后插入数据库,然后再清空一直循环
    private List<TestExcel> testExcelListFalse = new ArrayList<>();//校验失败、保存数据库失败的数据集合,可以插入到一个失败数据表,或者显示在前端提醒用户哪些数据导入失败
    /**
     * 很明显,onException这个就是用来处理异常的,当其它侦听器出现异常时会触发此方法
     * @param e Exception
     * @param analysisContext analysisContext
     * Exception 默认是直接抛出异常的,要注意处理异常
     */
    @Override
    public void onException(Exception e, AnalysisContext analysisContext) {
        log.info("兄嘚,你的代码出现异常了!");
        e.printStackTrace();
    }

    /**
     * 获取excel的第一行head数据
     * @param map 数据map
     * @param analysisContext analysisContext
     */
    @Override
    public void invokeHead(Map<Integer, CellData> map, AnalysisContext analysisContext) {
        log.info("第一列:{} 第二列:{} 第三列:{}",map.get(0).getStringValue(),map.get(1).getStringValue(),map.get(2).getStringValue());

    }

    /**
     * 读取正文数据,一次只读取一行
     * @param testExcel 实体类对象
     * @param analysisContext analysisContext
     */
    @Override
    public void invoke(TestExcel testExcel, AnalysisContext analysisContext) {
        log.info("读取到一条数据:{}", JSON.toJSONString(testExcel));
        //因为是测试,这里只做一些简单的为空判断,正式的可以根据业务需求自己写校验条件
        if(testExcel == null){//对象为空直接跳出
            return;
        }else if(StringUtils.isBlank(testExcel.getUserName())){//判断名字是否为空
            testExcelListFalse.add(testExcel);//放入错误集合列表
            return;
        }else if(StringUtils.isBlank(testExcel.getUserCardid())){//判断身份证是否为空
            /**
             * 身份证号的判断,以前碰到过一个需求,就是不能和数据库已有的数据重复,数据库存在这个身份证号则表示数据已导入/不能再次导入
             * 我使用的方式是,先把那个表的”身份证号“字段全部查询出来加载到内存里,然后这里直接和查询出来的身份证号进行对比,存在的就不
             * 导入,并记录到错误集合列表标注为”重复导入“,不存在的才存入正确的集合列表,并把这个身份证号也存入内存,给后面的数据校验
             * 是否有重复的数据,这样所有的校验都在内存里进行,优点是:速度会很快、数据库压力也会很小,但是缺点也很明显:很占内存。
             * 不过通过测试:数据在百万级的,只查询身份证号的话,内存的占用是很少的,即使是微型服务器也能满足需求,而如果是千万级数据,相信
             * 能有这个数据量的,服务器也差不了,上亿数据量的还没处理过,以后有机会碰到了再进行测试吧,这里不进行身份证号相同的校验
             */
            testExcelListFalse.add(testExcel);//放入错误集合列表
            return;
        }
        testExcelListTure.add(testExcel);//校验通过的方法正确集合列表
        if(count <= testExcelListTure.size()){//集合数据大于设定的数量时,提交数据库保存
            testExcelService.saveBatch(testExcelListTure);//批量存入数据库
            testExcelListTure = new ArrayList<>();//清空正确列表数据,再次循环
        }

    }

    /**
     * 额外单元格返回的数据,这个方法还没详细了解过,一直没用到过
     * @param cellExtra cellExtra对象
     * @param analysisContext analysisContext
     */
    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
        log.info("extra:{}",JSON.toJSONString(cellExtra));
    }

    /**
     * 当读取完所有数据后就会执行次方法
     * @param analysisContext analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("兄嘚,所有数据读取完了哦!");
        //读取完excel后再次判断是否还要未存入数据库的数据
        if(testExcelListTure.size() > 0){
            testExcelService.saveBatch(testExcelListTure);//不为空,则存入数据库
        }
        //这里也可以处理错误列表,保存入错误列表数据库,或者显示到前端给用户查看
    }

    /**
     * 这个方法最坑,默认返回的是false,一定要记得改成true,为false时会只返回一条数据,
     * 意思是只会执行invokeHead方法
     * @param analysisContext analysisContext
     * @return 是否读取下一行
     */
    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        return true;
    }
}

4、使用for循环创建数据,并导出成excel文件(controller的依赖全放在这里了,后面不再重复写)

由于100万条数据量太多了,所以这里先使用代码生成100万条数据,再测试导入和导出

package com.bug.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bug.entity.TestExcel;
import com.bug.entity.WxConfig;
import com.bug.mapper.excel.TestExcelMapper;
import com.bug.service.excel.TestExcelService;
import com.bug.service.wxconfig.WxConfigService;
import com.bug.util.excel.ExcelUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * EasyExcel 普通导入excel
 */
@RequestMapping("/public")
@RestController
public class publicController {

    public static final Logger log = LoggerFactory.getLogger(publicController.class);
    @Resource
    private WxConfigService wxConfigService;
    @Resource
    private TestExcelService testExcelService;
    @Resource
    private HttpServletRequest request;
    @Resource
    private HttpServletResponse response;
    @Resource
    private TestExcelMapper testExcelMapper;

    /**
     * 1、EasyExcel excel导出(for循环生成数据导出,百万级数据测试)
     * 相当于一次查询出一百万条数据,然后直接导出(此方式只适合少量数据,会很消耗内存)
     */
    @GetMapping("/exportForExcel")
    public void exportForExcel() {
        log.info("for循环导出excel。。。");
        //需要导出的数据集合
        List<TestExcel> testExcelList = new ArrayList<>();
        long number = 100000000000000000L;
        for(int i=1; i<=1000000; i++){
            TestExcel testExcel = new TestExcel();
            testExcel.setUserAge(String.valueOf(i));
            testExcel.setUserCardid(String.valueOf(number+i));
            testExcel.setUserName("张三"+i);
            testExcelList.add(testExcel);
        }

        try {
            //下方使用了用户自己选择文件保存路径的方式,所以需要配请求参数,如果使用固定路径可忽略此代码
            String filename = URLEncoder.encode(System.currentTimeMillis()+".xlsx","UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-disposition", "attachment; filename=" + filename);//设定输出文件头
            response.setContentType("application/x-xls");// 定义输出类型

            //不需要导出的字段userId,如果没有不需要导出的字段,可以忽略这个方法
            //只需要导入的字段,用includeColumnFiledNames()方法,写法是一样的
            Set<String> excludeColumnFiledNames = new HashSet<String>();
            excludeColumnFiledNames.add("userId");
            /*系统指定文件的输出路径
                String fileName = "D:/test/"+System.currentTimeMillis()+".xlsx";
                指定路径使用写法EasyExcel.write(fileName,TestExcel.class)
                EasyExcel.write()有很多种方法,其它方法可以直接查看源码
            */
            //这里采用用户自己选择文件保存路径的方式
            OutputStream out = response.getOutputStream();
            //开始导出,
            EasyExcel.write(out,TestExcel.class).excludeColumnFiledNames(excludeColumnFiledNames)
                    .sheet("测试模板")
                    .doWrite(testExcelList);
        }catch (Exception e){
            log.info("兄嘚,你代码又出错啦");
            e.printStackTrace();
        }

    }


}

5、 excel导入

/**
     * 2、EasyExcel excel导入(百万级数据测试)
     * @return
     */
    @PostMapping("/submitExcel")
    public void submitExcel(MultipartFile file){
        log.info("开始导入excel。。。");
        /**
         * EasyExcel.read有很多方法,也可以直接传入路径
         * String fileUrl = "D:/test/test.xlsx";
         */
        //创建reader
        ExcelReader excelReader = null;
        try {
            excelReader = EasyExcel.read(file.getInputStream(), TestExcel.class, new ExcelUpload(testExcelService)).build();
            // 构建sheet,可以指定是第几个sheet
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            // 读取sheet
            excelReader.read(readSheet);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (excelReader != null) {
                //这里不能省略
                excelReader.finish();
            }
        }
    }

6、excel导出

    /**
     * 3、EasyExcel excel导出(分页查询数据导出,百万级数据测试)
     * 采用分页查询的方式导出excel,内存占用很少,数据量大的时候推荐使用此方式
     */
    @GetMapping("/exportSqlExcel")
    public void exportSqlExcel() {
        log.info("sql分页导出excel。。。");
        //分页查询,每次查询量,这个插件最大只允许一次查500或者直接查全部,要想查更多的就只能去改源码了
        int pageNum = 500;
        ExcelWriter excelWriter = null;
        try {
            //下方使用了用户自己选择文件保存路径的方式,所以需要配请求参数,如果使用固定路径可忽略此代码
            String filename = URLEncoder.encode(System.currentTimeMillis()+".xlsx","UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-disposition", "attachment; filename=" + filename);//设定输出文件头
            response.setContentType("application/x-xls");// 定义输出类型

            //不需要导出的字段userId,如果没有不需要导出的字段,可以忽略这个方法
            //只需要导入的字段,用includeColumnFiledNames()方法,写法是一样的
            //Set<String> excludeColumnFiledNames = new HashSet<String>();
            //excludeColumnFiledNames.add("userId");
            //这里采用用户自己选择文件保存路径的方式
            OutputStream out = response.getOutputStream();
            //这里其实就是把上面的方法分开写,写入同一个sheet
            excelWriter = EasyExcel.write(out, TestExcel.class).build();
            WriteSheet writeSheet = EasyExcel.writerSheet("测试模板呀").build();
            //重点是这里的循环调用---分页查询,先查询出需要导出的总数
            long count = testExcelService.count();
            int num = (int)(count / pageNum) + 1;
            for (int i = 1; i < num; i++) {
                //分页查询
                Page<TestExcel> page = new Page<>(i,pageNum);
                IPage<TestExcel> testExcelIPage = testExcelService.page(page);
                List<TestExcel> testExcelList = testExcelIPage.getRecords();
                if(testExcelList.size() > 0){
                    //导出
                    excelWriter.write(testExcelList, writeSheet);
                }
            }
        }catch (Exception e){
            log.info("兄嘚,你代码又出错啦");
            e.printStackTrace();
        }finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }

    }

四、总结

EasyExcel本身的读写速度是非常快的,如上:导入100万条数据3个字段,只需要3-4分钟即可完成,for循环导出100万条只需要2分钟。

真正影响速度的其实是:你的批量插入方法和你的分页查询的速度,打个比方,就最上面的分页查询导出,循环对100万条分页查询,

一次只查500条,整个的导出需要15分钟左右,查询就用了12-13分钟左右。最后看看效果吧!

注:原文创作不易,转发的请带上此原文链接,并标明出处。

猜你喜欢

转载自blog.csdn.net/xiaobug_zs/article/details/123177268