栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【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文件

主要依赖是:


     com.alibaba
     easyexcel
     2.2.10

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



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.4.3
         
    
    com.bug
    bug
    0.0.1-SNAPSHOT
    my_springboot1
    Demo project for Spring Boot
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            com.google.zxing
            core
            3.3.0
        
        
            com.google.zxing
            javase
            3.3.3
        
        
            org.testng
            testng
            RELEASE
            compile
        
        
        
            com.itextpdf
            itextpdf
            5.5.6
        
        
            com.itextpdf
            itext-asian
            5.2.0
        
        
        
        
            junit
            junit
            4.13
        
        
        
        
            org.slf4j
            slf4j-api
            1.7.30
        
        
            ch.qos.logback
            logback-classic
            1.2.3
        
        
            junit
            junit
        
        
        
        
            com.alibaba
            fastjson
            1.2.76
        
        
        
        
            commons-io
            commons-io
            2.4
        
        
            org.apache.commons
            commons-lang3
            3.4
        
        
        
        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
            io.springfox
            springfox-swagger-ui
            2.9.2
        
        
        
        
            org.springframework.boot
            spring-boot-starter-jdbc
            2.5.3
        
        
            mysql
            mysql-connector-java
            5.1.38
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.3.2
        
        
        
        
            com.alibaba
            easyexcel
            2.2.10
        
        
        
        
            org.projectlombok
            lombok
            1.18.12
            provided
        
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                2.4.3
            
        

        
            
                src/main/java
                
                    ***.*
                
            
        

    


2、yml文件(导入、导出共用代码)
spring:
  devtools:
    restart:
      enabled: true  #设置开启热部署
      additional-paths: src/main/java #重启目录
      exclude: WEB-INF
  @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 {
}

③ service层(导入、导出共用代码)
package com.bug.service.excel;

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

public interface TestExcelService extends IService {

}

④ 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 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 {

    
    @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;


public class ExcelUpload implements ReadListener {

    private 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 testExcelListTure = new ArrayList<>();//校验正确的数据集合,数量达到设定值后插入数据库,然后再清空一直循环
    private List testExcelListFalse = new ArrayList<>();//校验失败、保存数据库失败的数据集合,可以插入到一个失败数据表,或者显示在前端提醒用户哪些数据导入失败
    
    @Override
    public void onException(Exception e, AnalysisContext analysisContext) {
        log.info("兄嘚,你的代码出现异常了!");
        e.printStackTrace();
    }

    
    @Override
    public void invokeHead(Map map, AnalysisContext analysisContext) {
        log.info("第一列:{} 第二列:{} 第三列:{}",map.get(0).getStringValue(),map.get(1).getStringValue(),map.get(2).getStringValue());

    }

    
    @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<>();//清空正确列表数据,再次循环
        }

    }

    
    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
        log.info("extra:{}",JSON.toJSonString(cellExtra));
    }

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

    
    @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;


@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;

    
    @GetMapping("/exportForExcel")
    public void exportForExcel() {
        log.info("for循环导出excel。。。");
        //需要导出的数据集合
        List 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 excludeColumnFiledNames = new HashSet();
            excludeColumnFiledNames.add("userId");
            
            //这里采用用户自己选择文件保存路径的方式
            OutputStream out = response.getOutputStream();
            //开始导出,
            EasyExcel.write(out,TestExcel.class).excludeColumnFiledNames(excludeColumnFiledNames)
                    .sheet("测试模板")
                    .doWrite(testExcelList);
        }catch (Exception e){
            log.info("兄嘚,你代码又出错啦");
            e.printStackTrace();
        }

    }


}

5、 excel导入
    @PostMapping("/submitExcel")
    public void submitExcel(MultipartFile file){
        log.info("开始导入excel。。。");
        
        //创建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导出
    
    @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 excludeColumnFiledNames = new HashSet();
            //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 page = new Page<>(i,pageNum);
                IPage testExcelIPage = testExcelService.page(page);
                List 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分钟左右。最后看看效果吧!

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/749101.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号