1. 程式人生 > >以CSV檔案匯入MySQL的批量資料插入操作之Java操作

以CSV檔案匯入MySQL的批量資料插入操作之Java操作

最近工作涉及將excel中的資料匯入到MySQL資料庫,由於Excel中資料並不規範,需要進行二次加工。將excel中資料加工後,通過mybatis批量插入mySQL資料庫,其相關聯的技術點比較簡單,經過半天的編寫,算是把任務完成了。但測試時效能太差,處理2W條資料的excel檔案需要將近兩分鐘,後來在網上了解到了通過load data infile的方式,讓資料庫去載入csv資料檔案,效率能提高几十倍,所以小可打算嘗試一下,最終效果真的很不錯,載入5W條資料的excel檔案時間可以控制在6秒以內【小可已經很滿足了】,應該還可以優化,但是限於小可的能力,先把完成的工作做一個總結,也算是一個分享吧。

程式語言Java

平臺框架Spring、Spring MVC、MyBatis

解析ExcelApache POI

生成CSVApache commons-csv

一、解析excel

        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);
        }
其中resultList則為excel中所有資料,通過Mybatis進行批量插入資料庫處理
        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(",");
            }
        }
以上是通過Mybatis將資料插入的MySQL資料庫中,效能非常差。如果幾千條資料還能接受,上W條資料就會非常卡,一下通過生成CSV檔案操作方式,效能提升幾十倍

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