Java調用SqlLoader將大文本導入數據庫
業務場景:將一千萬條數據,大約500M的文本文檔的數據導入到數據庫
分析:通過Java的IO流解析txt文本文檔,拼接動態sql實現insert入庫,可以實現,缺點如下
第一:IO流解析大文本文件對機器性能要求較高,測試大約消耗2G左右的內存
第二:拼接sql語句insert一千萬條數據大約需要2小時時間,長時間insert會鎖表,如果是核心業務表,例如訂單表,會造成大量用戶無法下單,影響數據庫的性能
第三:這種操作可擴展性不強,每次只能針對指定的表,指定的列操作
針對以上缺點,現在通過接口調用數據庫系統命令實現,通過可視化界面,選擇要導入的表,要導入那些字段,上傳指定的txt文本,會自動生成對應的模板文件,實現大批量數據高效率的導入到數據庫,通過可配置化即可實現,相對前一種思路擴展性較強,
具體接口如下
1 package com.sun.sqlloader.api; 2 /** 3 * SqlLoader接口 4 * @ClassName: ISqlLoader 5 * @author sunt 6 * @date 2017年11月15日 7 * @version V1.0 8 */ 9 public interface ISqlLoader { 10 11 /** 12 * 自動生成控制文件 13 * @Title: ctlFileWriter 14 * @author sunt 15 * @date 2017年11月15日 16 * @param fileRoute 數據文件地址路徑(文件所在磁盤目錄) 17 * @param fileName 數據文件名 18 * @param tableName 表名 19 * @param fieldName 要寫入表的字段 20 * @param ctlfileName 控制文件名 21 * @return void 22 */ 23 void ctlFileWriter(String fileRoute,String fileName,String tableName,String fieldName,String ctlfileName); 24 25 /** 26 * 執行系統dos命令 27 * @Title: Executive 28 * @author sunt 29 * @date 2017年11月15日 30 * @param user 數據庫的用戶名 31 * @param pwd 數據庫的密碼 32 * @param database 連接數據庫的地址 33 * @param fileRoute 文件路徑 34 * @param ctlfileName 控制文件名 35 * @param logfileName 日志文件名 36 * @return void 37 */ 38 void Executive(String user,String pwd,String database,String fileRoute,String ctlfileName,String logfileName); 39 }
1 package com.sun.sqlloader.api.impl; 2 3 4 import java.io.BufferedReader; 5 import java.io.FileWriter; 6 import java.io.IOException; 7 import java.io.InputStream; 8 import java.io.InputStreamReader; 9 import java.nio.charset.Charset; 10 import java.util.Date; 11 12 import org.apache.log4j.Logger; 13 import org.springframework.stereotype.Service; 14 15 import com.sun.sqlloader.api.ISqlLoader; 16 /** 17 * SqlLoader接口實現 18 * @ClassName: SqlLoaderImpl 19 * @author sunt 20 * @date 2017年11月15日 21 * @version V1.0 22 */ 23 @Service 24 public class SqlLoaderImpl implements ISqlLoader{ 25 26 private Logger logger = Logger.getLogger(SqlLoaderImpl.class); 27 28 @Override 29 public void ctlFileWriter(String fileRoute, String fileName, String tableName, String fieldName,String ctlfileName) { 30 FileWriter fw = null; 31 String strctl = "OPTIONS (skip=0)" + // 0是從第一行開始 1是 從第二行 32 " LOAD DATA CHARACTERSET AL32UTF8 INFILE '"+fileRoute+""+fileName+"'" + //設置字符集編碼SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER = 'NLS_CHARACTERSET'; 33 " APPEND INTO TABLE "+tableName+"" + ////覆蓋寫入 34 " FIELDS TERMINATED BY '\\|'" + //數據中每行記錄用","分隔 ,TERMINATED用於控制字段的分隔符,可以為多個字符。|需要轉譯 35 " OPTIONALLY ENCLOSED BY \"'\"" + //源文件有引號 '',這里去掉 ''''" 36 " TRAILING NULLCOLS "+fieldName+""; //表的字段沒有對應的值時允許為空 源數據沒有對應,寫入null 37 try { 38 fw = new FileWriter(fileRoute + "" + ctlfileName); 39 fw.write(strctl); 40 } catch (IOException e) { 41 e.printStackTrace(); 42 } finally { 43 try { 44 fw.flush(); 45 fw.close(); 46 } catch (IOException e) { 47 logger.error("生成控制器文件異常..."); 48 e.printStackTrace(); 49 } 50 } 51 } 52 53 @Override 54 public void Executive(String user, String pwd, String database, String fileRoute, String ctlfileName,String logfileName) { 55 InputStream ins = null; 56 //要執行的DOS命令 --數據庫 用戶名 密碼 user/password@database 57 String dos="sqlldr "+user+"/"+pwd+"@"+database+" control="+fileRoute+""+ctlfileName+" log="+fileRoute+""+logfileName; 58 logger.info("執行的dos命令:" + dos); 59 String[] cmd = new String[] { "cmd.exe", "/C", dos }; // 命令cmd /c dir:是執行完dir命令后關閉命令窗口cmd /k dir:是執行完dir命令后不關閉命令窗口。 60 try { 61 Process process = Runtime.getRuntime().exec(cmd); 62 ins = process.getInputStream(); // 獲取執行cmd命令后的信息 63 64 BufferedReader reader = new BufferedReader(new InputStreamReader(ins,Charset.forName("GBK")));//解決dos下中文輸出亂碼 65 String line = null; 66 long startTime = new Date().getTime(); 67 while ((line = reader.readLine()) != null) { 68 logger.info("調用dos執行的結果==========>" + line); // 輸出 69 } 70 int exitValue = process.waitFor(); 71 if (exitValue == 0) { 72 logger.info("返回值:" + exitValue + "\n數據導入成功"); 73 logger.info("總共耗時:" + (new Date().getTime() - startTime) / 1000 + "秒"); 74 } else { 75 logger.info("返回值:" + exitValue + "\n數據導入失敗"); 76 } 77 78 process.getOutputStream().close(); // 關閉 79 } catch (Exception e) { 80 e.printStackTrace(); 81 } 82 } 83 84 }
生成測試數據的代碼
1 package com.sun.sqlloader; 2 3 import java.io.BufferedWriter; 4 import java.io.File; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.OutputStreamWriter; 8 9 /** 10 * 循環將數據按照指定的格式寫入文本文件 11 * @ClassName: OperaFile 12 * @author sunt 13 * @date 2017年11月15日 14 * @version V1.0 15 */ 16 public class OperaFile { 17 18 /** 19 * 寫數據到文件 20 * @Title: writeFile 21 * @author sunt 22 * @date 2017年11月15日 23 * @return void 24 */ 25 public static void writeFile(String filePath) throws IOException { 26 File fout = new File(filePath); 27 FileOutputStream fos = new FileOutputStream(fout); 28 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos)); 29 for (Long i = 0L; i < 10000000; i++) { 30 bw.write(i + "|測試數據"+i+"|"); 31 bw.newLine(); 32 } 33 bw.close(); 34 } 35 }
前台展示效果
只需要輸入:表名和字段名,上傳大文本文件提交即可
一千萬條數據測試結果如下:
執行結果:大約5分多鍾

數據庫結果:
