使用Springboot+poi上传并处理百万级数据EXCEL

1 Excel上传

针对Excel的上传,采用的是比较常规的方法,其实和文件上传是相同的。具体源码如下:

  @PostMapping(value = "", consumes = "multipart/*", headers = "content-type=multipart/form-data")
  public Map<String, Object> addBlacklist(
      @RequestParam("file") MultipartFile multipartFile, HttpServletRequest request
  ) {
    //判断上传内容是否符合要求
    String fileName = multipartFile.getOriginalFilename();
    if (!fileName.matches("^.+\\.(?i)(xls)$") && !fileName.matches("^.+\\.(?i)(xlsx)$")) {
      return returnError(0,"上传的文件格式不正确");
    }

    String file = saveFile(multipartFile, request);
    int result = 0;
    try {
      result = blacklistServcice.addBlackLists(file);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return returnData(result);
  }

  private String saveFile(MultipartFile multipartFile, HttpServletRequest request) {
    String path;
    String fileName = multipartFile.getOriginalFilename();
    // 判断文件类型
    String realPath = request.getSession().getServletContext().getRealPath("/");
    String trueFileName = fileName;
    // 设置存放Excel文件的路径
    path = realPath + trueFileName;
    File file = new File(path);
    if (file.exists() && file.isFile()) {
      file.delete();
    }
    try {
      multipartFile.transferTo(new File(path));
    } catch (IOException e) {
      e.printStackTrace();
    }

    return path;
  }

上面的源码我们可以看见有一个saveFile方法,这个方法是将文件存在服务器本地,这样方便后续文件内容的读取,用不着一次读取所有的内容从而导致消耗大量的内存。当然这里大家如果有更好的方法希望能留言告知哈。

2 Excel处理工具源码

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.InputStream;
import java.sql.SQLException;
import java.util.*;

/**
 * XSSF and SAX (Event API)
 */
public abstract class XxlsAbstract extends DefaultHandler {
  private SharedStringsTable sst;
  private String lastContents;
  private int sheetIndex = -1;
  private List<String> rowlist = new ArrayList<>();
  public List<Map<String, Object>> dataMap = new LinkedList<>(); //即将进行批量插入的数据
  public int willSaveAmount; //将要插入的数据量
  public int totalSavedAmount; //总共插入了多少数据
  private int curRow = 0;    //当前行
  private int curCol = 0;    //当前列索引
  private int preCol = 0;    //上一列列索引
  private int titleRow = 0;  //标题行,一般情况下为0
  public int rowsize = 0;  //列数

  //excel记录行操作方法,以sheet索引,行索引和行元素列表为参数,对sheet的一行元素进行操作,元素为String类型
  public abstract void optRows(int sheetIndex, int curRow, List<String> rowlist) throws SQLException;

  //只遍历一个sheet,其中sheetId为要遍历的sheet索引,从1开始,1-3

  /**
   * @param filename
   * @param sheetId sheetId为要遍历的sheet索引,从1开始,1-3
   * @throws Exception
   */
  public void processOneSheet(String filename, int sheetId) throws Exception {
    OPCPackage pkg = OPCPackage.open(filename);
    XSSFReader r = new XSSFReader(pkg);
    SharedStringsTable sst = r.getSharedStringsTable();

    XMLReader parser = fetchSheetParser(sst);

    // rId2 found by processing the Workbook
    // 根据 rId# 或 rSheet# 查找sheet
    InputStream sheet2 = r.getSheet("rId" + sheetId);
    sheetIndex++;
    InputSource sheetSource = new InputSource(sheet2);
    parser.parse(sheetSource);
    sheet2.close();
  }

  public XMLReader fetchSheetParser(SharedStringsTable sst)
      throws SAXException {
    XMLReader parser = XMLReaderFactory.createXMLReader();
    this.sst = sst;
    parser.setContentHandler(this);
    return parser;
  }

  public void endElement(String uri, String localName, String name) {
    // 根据SST的索引值的到单元格的真正要存储的字符串
    try {
      int idx = Integer.parseInt(lastContents);
      lastContents = new XSSFRichTextString(sst.getEntryAt(idx))
          .toString();
    } catch (Exception e) {

    }

    // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引
    // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符
    if (name.equals("v")) {
      String value = lastContents.trim();
      value = value.equals("") ? " " : value;
      int cols = curCol - preCol;
      if (cols > 1) {
        for (int i = 0; i < cols - 1; i++) {
          rowlist.add(preCol, "");
        }
      }
      preCol = curCol;
      rowlist.add(curCol - 1, value);
    } else {
      //如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法
      if (name.equals("row")) {
        int tmpCols = rowlist.size();
        if (curRow > this.titleRow && tmpCols < this.rowsize) {
          for (int i = 0; i < this.rowsize - tmpCols; i++) {
            rowlist.add(rowlist.size(), "");
          }
        }
        try {
          optRows(sheetIndex, curRow, rowlist);
        } catch (SQLException e) {
          e.printStackTrace();
        }
        if (curRow == this.titleRow) {
          this.rowsize = rowlist.size();
        }
        rowlist.clear();
        curRow++;
        curCol = 0;
        preCol = 0;
      }
    }
  }
}

3 解析成功后的数据处理

首先我们将源码展示出来,然后再具体说明

public int addBlackLists(String file) throws ExecutionException, InterruptedException {
    ArrayList<Future<Integer>> resultList = new ArrayList<>();
    XxlsAbstract xxlsAbstract = new XxlsAbstract() {

      //针对数据的具体处理
      @Override
      public void optRows(int sheetIndex, int curRow, List<String> rowlist) {

        /**
         * 判断即将插入的数据是否已经到达8000,如果到达8000,
         * 进行数据插入
         */
        if (this.willSaveAmount == 5000) {

          //插入数据
          List<Map<String, Object>> list = new LinkedList<>(this.dataMap);
          Callable<Integer> callable = () -> {
            int count = blacklistMasterDao.addBlackLists(list);
            blacklistRecordMasterDao.addBlackListRecords(list);
            return count;
          };
          this.willSaveAmount = 0;
          this.dataMap = new LinkedList<>();
          Future<Integer> future = executor.submit(callable);
          resultList.add(future);
        }

        //汇总数据
        Map<String, Object> map = new HashMap<>();
        map.put("uid", rowlist.get(0));
        map.put("createTime", rowlist.get(1));
        map.put("regGame", rowlist.get(2));
          map.put("banGame", rowlist.get(2));
        this.dataMap.add(map);
        this.willSaveAmount++;
        this.totalSavedAmount++;
      }
    };
    try {
      xxlsAbstract.processOneSheet(file, 1);
    } catch (Exception e) {
      e.printStackTrace();
    }

    //针对没有存入的数据进行处理
    if(xxlsAbstract.willSaveAmount != 0){
      List<Map<String, Object>> list = new LinkedList<>(xxlsAbstract.dataMap);
      Callable<Integer> callable = () -> {
        int count = blacklistMasterDao.addBlackLists(list);
        blacklistRecordMasterDao.addBlackListRecords(list);
        return count;
      };
      Future<Integer> future = executor.submit(callable);
      resultList.add(future);
    }

    executor.shutdown();
    int total = 0;
    for (Future<Integer> future : resultList) {
      while (true) {
        if (future.isDone() && !future.isCancelled()) {
          int sum = future.get();
          total += sum;
          break;
        } else {
          Thread.sleep(100);
        }
      }
    }
    return total;
  }

针对上面的源码,我们可以发现,我们需要将读取到的EXCEL数据插入到数据库中,这里为了减小数据库的IO和提高插入的效率,我们采用5000一批的批量插入(注意:如果数据量过大会导致组成的SQL语句无法执行)。

这里需要获取到一个最终执行成功的插入结果,并且插入执行很慢。所有采用了Java多线程的Future模式,采用异步的方式最终来获取J执行结果。

通过上面的实现,楼主测试得到最终一百万条数据需要四分钟左右的时间就可以搞定。如果大家有更好的方法,欢迎留言。

补充知识:Java API SXSSFWorkbook导出Excel大批量数据(百万级)解决导出超时

之前使用简单的HSSFWorkbook,导出的数据不能超过

后来改成SXSSFWorkbook之后可以导出更多,但是

而且我之前的代码是一次性查出所有数据,几十万条,直接就超时了。

之前的代码是一次性查出所有的结果,list里面存了几十万条数据。因为功能设计的问题,我这一个接口要同时处理三个功能:

再加上查询SQL的效率问题,导致请求超时。

现在为了做到处更大量的数据只能选择优化。优化查询的sql这里就不讲了,只讲导出功能的优化。

其实就是分批次处理查询结果:

这样做的好处是查询速度变快,封装速度也变快,整体速度变快就不会出现超时,而且,每次分页查出的结果放到list中不会出现占用JVM内存过大的情况。避免出现内存溢出导致系统崩溃。

再次优化:

上面这样做虽然可以导出,但是代码看起来不美观:

这样看起来就简洁很多了。

经验证,查询加封装EXCEL7000条数据处理只需要1秒

以上这篇使用Springboot+poi上传并处理百万级数据EXCEL就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java POI实现将导入Excel文件的示例代码

    问题描述 现需要批量导入数据,数据以Excel形式导入. POI介绍 我选择使用的是apache POI.这是有Apache软件基金会开放的函数库,他会提供API给java,使其可以对office文件进行读写. 我这里只需要使用其中的Excel部分. 实现 首先,Excel有两种格式,一种是.xls(03版),另一种是.xlsx(07版).针对两种不同的表格格式,POI对应提供了两种接口.HSSFWorkbook和XSSFWorkbook 导入依赖 <dependency> <group

  • java使用POI批量导入excel数据的方法

    一.定义 Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能. 二.所需jar包: 三.简单的一个读取excel的demo 1.读取文件方法 /** * 读取出filePath中的所有数据信息 * @param filePath excel文件的绝对路径 * */ public static void getDataFromExcel(String filePath) { //String fileP

  • Java实现excel大数据量导入

    本文实例为大家分享了Java实现excel大数据量导入的具体代码,供大家参考,具体内容如下 情景分析: 通常我们通过poi读取excel文件时,若在用户模式下,由于数据量较大.Sheet较多,很容易出现内存溢出的情况 用户模式读取excel的典型代码如下: FileInputStream file = new FileInputStream("c:\\test.xlsx"); Workbook wb=new XSSFWorkbook(file); 而03版(xls)excel文件每个s

  • SpringBoot中EasyExcel实现Excel文件的导入导出

    前言 在我们日常的开发过程中经常会使用Excel文件的形式来批量地上传下载系统数据,我们最常用的工具是Apache poi,但是如果数据到底上百万时,将会造成内存溢出的问题,那么我们怎么去实现百万数据批量导入导出. 正文 Easyexcel Easyexcel 是阿里巴巴的开源项目,用来优化Excel文件处理过程: poi消耗内存严重:Java解析.生成Excel比较有名的框架有Apache poi.jxl.但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的

  • 使用Springboot+poi上传并处理百万级数据EXCEL

    1 Excel上传 针对Excel的上传,采用的是比较常规的方法,其实和文件上传是相同的.具体源码如下: @PostMapping(value = "", consumes = "multipart/*", headers = "content-type=multipart/form-data") public Map<String, Object> addBlacklist( @RequestParam("file&quo

  • SpringBoot文件上传控制及Java 获取和判断文件头信息

    之前在使用SpringBoot进行文件上传时,遇到了很多问题.于是在翻阅了很多的博文之后,总算将上传功能进行了相应的完善,便在这里记录下来,供自己以后查阅. 首先,是建立一个标准的SpringBoot 的工程,这里使用的IDE是Intellij Idea,为了方便配置,将默认的配置文件替换为了application.yml. 1.在index.html中进行文件上传功能,这里使用的文件上传方式是ajax,当然也可以按照自己的具体要求使用传统的表单文件上传. <!DOCTYPE html> &l

  • SpringBoot 文件上传和下载的实现源码

    本篇文章介绍SpringBoot的上传和下载功能. 一.创建SpringBoot工程,添加依赖 compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-thymeleaf") 工程目录为: Application.java 启动类 package hello; import org.springf

  • 浅析SpringBoot打包上传到docker并实现多实例部署(IDEA版)

    最近有一个朋友问我有没有玩过docker,回答起来底气不是很足 ^ - ^ (刚开始玩cloud的时候接触过,玩过一会,后面在实际开发中比较少用,所以基本上可以说是忘记了.),今天花了点时间整理之前的笔记以及整理走一个流程,记录一下吧,下次玩得时候C V 就好啦~ 1. Docker的安装以及开启远程访问 1.1 安装 # 检查虚拟机内核版本,必须是3.10及以上 uname -r # 安装docker yum install docker # 输入y确认安装 # 启动docker system

  • 解决springboot项目上传文件出现临时文件目录为空的问题

    最近写文件上传到服务器读取的代码,前端使用FormData上传,服务端用MultipartFile接收,自己测试了下MultipartFile对象有什么东西,结果一般属性都能出来,测试getInputStrea()方法的时候出现了以下错误,简单一看这是什么目录,从来没见过啊: 百度一番之后发现了这是临时文件存放的路径,不清楚根据什么去放在这个目录的,但是这个目录本机的确没有,linux系统下好像系统会10天清空一次,翻阅了API底下有这个 百度的时候也发现另外一个坑,spring配置的MaxIn

  • SpringBoot实现上传文件到AWS S3的代码

    简单记录一下在Springboot中上传文件到AWS S3存储服务的代码. 在 application.xml中添加aws相关配置: custom:   aws:     access-key: CHOBITACCESSKEY     secret-key: CHOBIT/THISIS006SECRET007Key/dotORG     bucket: zhyea     endpoint: www.zhyea.com:80 新建一个 AwsS3Componment类来执行上传文件操作: @Co

  • 解决SpringBoot文件上传临时目录找不到的问题

    SpringBoot文件上传临时目录问题 我相信大家在把项目部署到服务器上面时候都会遇到这样一个问题: org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request;nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.133776721859504205

  • Springboot文件上传出现找不到指定系统路径的解决

    目录 Springboot文件上传出现找不到指定系统路径 1.问题描述 2.问题分析 3.问题解决方案 SpringBoot 上传文件时本地路径无效 错误产生的原因 解决方式有以下几点 Springboot文件上传出现找不到指定系统路径 1.问题描述 关键字:SpringMVC 4.2.4.Spring Boot 1.3.1.Servlet 3.0.文件上传 报错信息: java.io.IOException: java.io.FileNotFoundException: /tmp/tomcat

  • springboot文件上传保存路径的问题

    目录 springboot文件上传保存路径 配置代码如下 Springboot上传文件的问题(上传到本地文件夹中) 先建立一个controller包 静态资源目录如下 springboot文件上传保存路径 最近使用springboot整合富文本编辑器wangeditor,在整合的时候,对于图片上传时候保存路径出现了一些问题,代码如下 @PostMapping("/upload") public Result upload(MultipartFile[] file, HttpServle

  • Vue+Element+Springboot图片上传的实现示例

    最近没事刚好联系下vue+springboot前段后分离的项目.用上了图片上传功能.记录一下. 前端待提交的表单部分代码 <el-form-item label="封面图片"> <el-upload v-model="dataForm.title" class="avatar-uploader" :limit="1" list-type="picture-card" :on-preview

随机推荐