基於POIExcel匯入50+萬條資料的基本寫法
基本思路如下
poi 基於xml解析(event user model )
多執行緒批量插入
軟體環境
Springboot2.0 +
MyBatis
Mysql 5.7 +
poi 基於xml解析(event user model )
package com.sunducation.waterflow.utils;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import com.sunducation.waterflow.dto.DataDTO;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
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 javax.xml.crypto.Data;
public class ParseXMLUtil {
private final BlockingQueue<DataDTO> results ;
public ParseXMLUtil(BlockingQueue<DataDTO> results) {
this.results = results;
}
public void processFirstSheet(String filename) throws Exception {
OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ);
try {
XSSFReader r = new XSSFReader(pkg);
SharedStringsTable sst = r.getSharedStringsTable();
XMLReader parser = fetchSheetParser(sst);
// process the first sheet
InputStream sheet2 = r.getSheetsData().next();
InputSource sheetSource = new InputSource(sheet2);
parser.parse(sheetSource);
sheet2.close();
} finally {
pkg.close();
}
}
public void processAllSheets(String filename) throws Exception {
OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ);
try {
XSSFReader r = new XSSFReader(pkg);
SharedStringsTable sst = r.getSharedStringsTable();
XMLReader parser = fetchSheetParser(sst);
Iterator<InputStream> sheets = r.getSheetsData();
while (sheets.hasNext()) {
System.out.println("Processing new sheet:\n");
InputStream sheet = sheets.next();
InputSource sheetSource = new InputSource(sheet);
parser.parse(sheetSource);
sheet.close();
}
} finally {
pkg.close();
}
}
public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException {
XMLReader parser = XMLReaderFactory.createXMLReader();
ContentHandler handler = new SheetHandler(sst,this.results);
parser.setContentHandler(handler);
return parser;
}
/**
* See org.xml.sax.helpers.DefaultHandler javadocs
*/
private static class SheetHandler extends DefaultHandler {
private final SharedStringsTable sst;
private String lastContents;
private boolean nextIsString;
private boolean inlineStr;
private String row ="";
private final BlockingQueue<DataDTO> results;
private DataDTO dto = new DataDTO();
private final LruCache<Integer,String> lruCache = new LruCache<Integer,String>(50);
private static class LruCache<A,B> extends LinkedHashMap<A, B> {
private final int maxEntries;
public LruCache(final int maxEntries) {
super(maxEntries + 1, 1.0f, true);
this.maxEntries = maxEntries;
}
@Override
protected boolean removeEldestEntry(final Map.Entry<A, B> eldest) {
return super.size() > maxEntries;
}
}
private SheetHandler(SharedStringsTable sst,final BlockingQueue<DataDTO> results) {
this.sst = sst;
this.results = results;
}
@Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
// c => cell
if(name.equals("c")) {
// Print the cell reference
row = attributes.getValue("r") ;
String cellType = attributes.getValue("t");
nextIsString = cellType != null && cellType.equals("s");
inlineStr = cellType != null && cellType.equals("inlineStr");
}
// Clear contents cache
lastContents = "";
}
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
// Process the last contents as required.
// Do now, as characters() may be called more than once
if(nextIsString) {
Integer idx = Integer.valueOf(lastContents);
lastContents = lruCache.get(idx);
if (lastContents == null && !lruCache.containsKey(idx)) {
lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
lruCache.put(idx, lastContents);
}
nextIsString = false;
}
// v => contents of a cell
// Output after we've seen the string contents
if(name.equals("v") || (inlineStr && name.equals("c"))) {
char rowC=row.substring(0,1).charAt(0);
int rowNum=Integer.parseInt(row.substring(1));
if(rowNum <=1){
return;
}
switch (rowC){
case 65:
dto.setNo(Integer.parseInt(lastContents.toString()));
break;
case 66:
dto.setGrade((String) lastContents);
break;
case 67:
dto.setName((String) lastContents);
break;
case 68:
dto.setAge(Integer.parseInt(lastContents.toString()));
break;
}
}
if(name.equals("row")){
int rowNum=Integer.parseInt(row.substring(1));
if(rowNum <=1){
return;
}
results.offer(dto);
dto = new DataDTO();
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException { // NOSONAR
lastContents += new String(ch, start, length);
}
}
public static void main(String[] args) throws Exception {
long startMini = System.currentTimeMillis();
BlockingQueue<DataDTO> results = new ArrayBlockingQueue<DataDTO>(16);
ParseXMLUtil xmlUtil = new ParseXMLUtil(results);
xmlUtil.processFirstSheet("C:/Users/92039/Desktop/print/demo2.xlsx");
System.out.println(results.size());
long endMini = System.currentTimeMillis();
System.out.println(" take time is " + (endMini - startMini)/1000.0);
}
}
多執行緒批量插入
Mapper.xml
<!-- 批量儲存使用者,並返回每個使用者插入的ID -->
<insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
INSERT INTO import_data (no,name,grade,age)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.no}, #{item.name},#{item.grade},#{item.age})
</foreach>
</insert>
Service 層
package com.sunducation.waterflow.service.impl;
import com.sunducation.waterflow.dto.DataDTO;
import com.sunducation.waterflow.muti.InsertDataTask;
import com.sunducation.waterflow.service.ImportDataService;
import com.sunducation.waterflow.utils.ParseXMLUtil;
import org.springframework.stereotype.Service;
import java.util.concurrent.*;
/**
* @version 1.0
* @description:
* @projectName: com.sunducation.rbac.service.impl
* @className: rbac
* @author:tannc
* @createTime:2018/8/2 14:26
*/
@Service
public class ImportDataServiceImpl implements ImportDataService {
@Override
public double parseAndinsertDb(String filepath) throws Exception {
//
final int maxThreadNums = 5;
long startMini = System.currentTimeMillis();
final BlockingQueue<DataDTO> results = new LinkedBlockingQueue<DataDTO>();
// 多執行緒插入
final CyclicBarrier barrier = new CyclicBarrier(maxThreadNums+1);
ExecutorService executorService = Executors.newFixedThreadPool(maxThreadNums);
executorService.execute(new Runnable() {
@Override
public void run() { // 解析
ParseXMLUtil xmlUtil = new ParseXMLUtil(results);
try {
xmlUtil.processFirstSheet(filepath);
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
});
Thread.sleep(500);
for(int i =1 ;i<maxThreadNums;i++){
// 提交任務
executorService.execute(new InsertDataTask(results, barrier, "task"+i));
}
barrier.await();
executorService.shutdown();
// System.out.println("執行完成");
// System.out.println(results.size());
long endMini = System.currentTimeMillis();
// System.out.println(" take time is " + (endMini - startMini)/1000.0);
return (endMini - startMini)/1000.0;
}
}
任務層 InsertDataTask
package com.sunducation.waterflow.muti;
import com.sunducation.waterflow.dao.mapper.ImportDataMapper;
import com.sunducation.waterflow.dto.DataDTO;
import com.sunducation.waterflow.utils.SpringContextUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @version 1.0
* @description: 另外執行緒插入
* @projectName: com.kongxiang.muti
* @className: DesignModel
* @author:tannc
* @createTime:2018/9/15 9:36
*/
public class InsertDataTask implements Runnable {
private final ImportDataMapper importDataMapper;
private final BlockingQueue<DataDTO> queue;
private final CyclicBarrier barrier;
private final String taskName ;
public InsertDataTask(BlockingQueue<DataDTO> queue, CyclicBarrier barrier,String taskName) {
this.queue = queue;
this.barrier = barrier;
this.taskName =taskName;
importDataMapper = SpringContextUtils.getBean("importDataMapper");
}
@Override
public void run() {
List<DataDTO> list = new ArrayList<DataDTO>(256);
final ReentrantLock lock = new ReentrantLock();
while (!queue.isEmpty()) {
lock.lock();
list.clear();
// 出棧
queue.drainTo(list,10000);
//分批處理
if(null!=list&&list.size()>0){
// System.err.println(taskName + " 插入size +++> " + list.size());
int pointsDataLimit = 10000;//限制條數
Integer size = list.size();
//判斷是否有必要分批
if(pointsDataLimit<size){
int part = size/pointsDataLimit;//分批數
// System.out.println("共有 : "+size+"條,!"+" 分為 :"+part+"批");
//
for (int i = 0; i < part; i++) {
//1000條
List<DataDTO> listPage = list.subList(0, pointsDataLimit);
importDataMapper.batchInsert(listPage);
//剔除
list.subList(0, pointsDataLimit).clear();
}
if(!list.isEmpty()){
//表示最後剩下的資料
importDataMapper.batchInsert(list);
}
}else{
importDataMapper.batchInsert(list);
}
} // end if
if (queue.isEmpty()){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.unlock();
} // while
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
Controller 層
package com.sunducation.waterflow.controller;
import com.sunducation.waterflow.ResultMsg;
import com.sunducation.waterflow.service.ImportDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
/**
* @version 1.0
* @description:
* @projectName: 上傳檔案
* @className: 上傳檔案
* @author:tannc
* @createTime:2018/9/6 19:32
*/
@RestController
@RequestMapping("/api/v1/uploads")
public class UploadFileController {
@Autowired
private ImportDataService importDataService ;
//處理檔案上傳
@RequestMapping(value="/excel", method = RequestMethod.POST)
public ResultMsg uploadImg(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {
if (!file.isEmpty()) {
//
String filename = file.getOriginalFilename();
String prefix=filename.substring(filename.lastIndexOf(".")+1);
// Excel 2007版9
if( null != prefix && prefix.equalsIgnoreCase("xlsx")){
// 返回記錄數
double count= 0.0d;
try {
File f = null;
f=File.createTempFile("tmp", null);
file.transferTo(f);
f.deleteOnExit();
// 檔案路徑
count = importDataService.parseAndinsertDb(f.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
return new ResultMsg(0,"檔案上傳成功,共花費 " + count +" 秒");
}
else{
return new ResultMsg(400,"請選擇檔案");
}
} else {
return new ResultMsg(400,"檔案不存在");
}
}
}
完整程式碼下載地址:https://github.com/tannongchun/bigdataimport.git
---------------------
作者:春馨夢
來源:CSDN
原文:https://blog.csdn.net/tannongchun/article/details/82729635
版權宣告:本文為博主原創文章,轉載請附上博文連結!