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

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

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

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

1 Excel上传

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

  @PostMapping(value = "", consumes = "multipart
public abstract class XxlsAbstract extends DefaultHandler {
  private SharedStringsTable sst;
  private String lastContents;
  private int sheetIndex = -1;
  private List rowlist = new ArrayList<>();
  public List> 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 rowlist) throws SQLException;
 
  //只遍历一个sheet,其中sheetId为要遍历的sheet索引,从1开始,1-3
 
  
  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> resultList = new ArrayList<>();
    XxlsAbstract xxlsAbstract = new XxlsAbstract() {
 
      //针对数据的具体处理
      @Override
      public void optRows(int sheetIndex, int curRow, List rowlist) {
 
 
 if (this.willSaveAmount == 5000) {
 
   //插入数据
   List> list = new linkedList<>(this.dataMap);
   Callable callable = () -> {
     int count = blacklistMasterDao.addBlackLists(list);
     blacklistRecordMasterDao.addBlackListRecords(list);
     return count;
   };
   this.willSaveAmount = 0;
   this.dataMap = new linkedList<>();
   Future future = executor.submit(callable);
   resultList.add(future);
 }
 
 //汇总数据
 Map 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> list = new linkedList<>(xxlsAbstract.dataMap);
      Callable callable = () -> {
 int count = blacklistMasterDao.addBlackLists(list);
 blacklistRecordMasterDao.addBlackListRecords(list);
 return count;
      };
      Future future = executor.submit(callable);
      resultList.add(future);
    }
 
    executor.shutdown();
    int total = 0;
    for (Future 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就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持考高分网。

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

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

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