需求:测试EasyExcel的导入导出使用、性能,测试数据量3个字段100万条数据;
测试环境:idea + maven + springboot + mybatis-plus + mysql + swagger
文章目录(目录跳转可能会不准确,建议直接Ctrl+F搜目录吧)
⑥ EasyExcel的工具类(最主要的文件,如果使用的其它框架,可以忽略上面的代码,注释会尽量写的详细一些)
4、使用for循环创建数据,并导出成excel文件(controller的依赖全放在这里了,后面不再重复写)
前言
导入流程:用户上传文件-->后端获取文件流-->侦听器读取文件-->批量插入数据库
导出流程:用户点击按钮-->调用后端接口-->分页查询需要导出的数据-->导出成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分钟左右。最后看看效果吧!
注:原文创作不易,转发的请带上此原文链接,并标明出处。