以CSV檔案匯入MySQL的批量資料插入操作之Java操作
最近工作涉及將excel中的資料匯入到MySQL資料庫,由於Excel中資料並不規範,需要進行二次加工。將excel中資料加工後,通過mybatis批量插入mySQL資料庫,其相關聯的技術點比較簡單,經過半天的編寫,算是把任務完成了。但測試時效能太差,處理2W條資料的excel檔案需要將近兩分鐘,後來在網上了解到了通過load data infile的方式,讓資料庫去載入csv資料檔案,效率能提高几十倍,所以小可打算嘗試一下,最終效果真的很不錯,載入5W條資料的excel檔案時間可以控制在6秒以內【小可已經很滿足了】,應該還可以優化,但是限於小可的能力,先把完成的工作做一個總結,也算是一個分享吧。
程式語言:Java
平臺框架:Spring、Spring MVC、MyBatis
解析Excel:Apache POI
生成CSV:Apache commons-csv
一、解析excel
其中resultList則為excel中所有資料,通過Mybatis進行批量插入資料庫處理Workbook workBook = parseExcelGetWorkbook(is,fileName); Sheet sheet = workBook.getSheetAt(0); int rowsNum = sheet.getPhysicalNumberOfRows(); List<List<String>> resultList = Lists.newArrayList(); Row rootRow = sheet.getRow(0); int cellsNum = rootRow.getPhysicalNumberOfCells(); for(int j=1;j<rowsNum;j++){ Row row = sheet.getRow(j); List<String> list = new ArrayList<String>(); for(int i=0;i<cellsNum;i++){ Cell cell = row.getCell(i); if(cell!=null){ cell.setCellType(Cell.CELL_TYPE_STRING); list.add(StringUtils.noNull(cell.getStringCellValue())); }else{ list.add(""); } } resultList.add(list); }
以上是通過Mybatis將資料插入的MySQL資料庫中,效能非常差。如果幾千條資料還能接受,上W條資料就會非常卡,一下通過生成CSV檔案操作方式,效能提升幾十倍StringBuffer sb = new StringBuffer("insert into ").append(tableName).append("("); StringBuilder messageFormatStr = new StringBuilder("("); for(int i=0;i<columnList.size();i++){ if(i!=0){ sb.append(","); messageFormatStr.append(","); } sb.append(columnList.get(i)); messageFormatStr.append("#'{'dataList[{0,number,#}]."+columnList.get(i)+"}"); } sb.append(") values "); messageFormatStr.append(")"); MessageFormat messageFormat = new MessageFormat(messageFormatStr.toString()); for(int i=0;i<dataList.size();i++){ sb.append(messageFormat.format(new Object[]{i})); if (i < dataList.size() - 1) { sb.append(","); } }
1.通過解析excel處理資料【解析excel部分省略】,然後根據資料生成.CSV檔案
準備commons-csv.jar
使用的API主要有CSVFormat.class和CSVPrinter.class
CSVFormat csvFileFormat = CSVFormat.DEFAULT.withRecordSeparator("\n");//建立CSVFormat ,每行記錄間隔符使用換行【\n】
說明:此處對CSV檔案的具體使用方式不做過多介紹,如果不瞭解可以查閱相關資料
建立檔案輸出物件Writer,此文中使用BufferedWriter
BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(filepath)),"UTF-8"));
說明:此處使用UTF-8編碼,這樣確保生成CSV檔案的編碼格式為UTF-8【根據專案環境自行配置utf-8,gbk....】,如果此處不做轉碼處理,可能導致csv檔案中的中文欄位不能正確的匯入mySQL資料庫當中
我的專案中不涉及到生成表頭,如果涉及到生成表頭的話,可以通過如下方式設定:
csvFilePrinter.printRecord(new String[]{"id","username","password","name","age"});
CSVPrinter csvFilePrinter = new CSVPrinter(fileWriter, csvFileFormat);//根據格式化物件和輸出物件建立CSV檔案的寫入物件
從Excel檔案中獲取資料,將資料進行處理,將處理後的資料通過csvFilePrinter物件寫入到目標檔案中
Workbook workBook = parseExcelGetWorkbook(is,fileName);
Sheet sheet = workBook.getSheetAt(0);
int rowsNum = sheet.getPhysicalNumberOfRows();
Row rootRow = sheet.getRow(0);
int cellsNum = rootRow.getPhysicalNumberOfCells();
StringBuilder recordStr = new StringBuilder();
for(int j=1;j<rowsNum;j++){
Row row = sheet.getRow(j);
recordStr.append(StringUtils.noNull(j)+"&%$");
for(int i=0;i<cellsNum;i++){
Cell cell = row.getCell(i);
if(cell!=null){
cell.setCellType(Cell.CELL_TYPE_STRING);
recordStr.append(StringUtils.noNull(cell.getStringCellValue())+"&%$");
}else{
recordStr.append("");
}
}
recordStr.deleteCharAt(recordStr.length()-1);
csvFilePrinter.printRecord(recordStr.toString());
recordStr.delete(0, recordStr.length()-1);
}
說明:recordStr.append(StringUtils.noNull(j)+"&%$");中的“&%$”是每個欄位間的分割符,因為我們處理的資料比較特殊,可以用資料不特殊可以使用逗號,空格只來的,比如recordStr.append(StringUtils.noNull(j)+",");或是recordStr.append(StringUtils.noNull(j)+"\t");都可以。
csvFilePrinter.printRecord(recordStr.toString());//寫入每行資料
這樣就生成了目標csv檔案,檔案地址則為上述的filepath,到此csv檔案生成完畢,剩下的工作則通過java操作資料庫,將csv檔案匯入到mySQL資料庫中,我們測試了兩種方式,分別為原生態JDBC操作和Mybatis操作,在此將兩種處理方式的程式碼貼上下來供大家參考:
JDBC方式:
DruidDataSource dataSource = SpringContextHolder.getBean("dataSource");
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = dataSource.getConnection();
String sql = "LOAD DATA LOCAL INFILE '" + filepath
+ "' INTO TABLE " + tableName + " "
+ " FIELDS TERMINATED BY '&%$'";
pstmt = conn.prepareStatement(sql);
if (pstmt.isWrapperFor(com.mysql.jdbc.Statement.class)) {
com.mysql.jdbc.PreparedStatement mysqlStatement = pstmt
.unwrap(com.mysql.jdbc.PreparedStatement.class);
result = mysqlStatement.executeUpdate();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if(conn != null){
conn.close();
}
if(pstmt != null){
pstmt.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
Mybatis方式:
將tableName和filepath作為引數,正常呼叫Mybatis功能即可,在此將我們的程式碼片段供大家參考
service中呼叫:
mapper.insertDataByCSVFile(filepath, tableName);
mapper介面:
@SelectProvider(type=DataFileSqlProvider.class,method="getInsertDataByCSVFileSql")
public void insertDataByCSVFile(@Param("filepath") String filepath, @Param("tableName") String tableName);
public String getInsertDataByCSVFileSql(Map<String,Object> param){
String filepath = StringUtils.noNull(param.get("filepath"));
String tableName = StringUtils.noNull(param.get("tableName"));
String sql = "LOAD DATA LOCAL INFILE '" + filepath
+ "' INTO TABLE " + tableName + " "
+ " FIELDS TERMINATED BY '&%$'";
return sql;
}
說明:如果是本地則LOAD DATA LOCAL INFILE
伺服器則為:
LOAD DATA INFILE