Java中實現大批量導入數據到MySQL——LOAD DATA LOCAL INFILE

Mysql load data的使用

數據庫中,最常見的寫入數據方式是通過SQL INSERT來寫入,另外就是通過備份文件恢復數據庫,這種備份文件在MySQL中是SQL腳本,實際上執行的還是在批量INSERT語句。

在實際中,常常會遇到兩類問題:一類是數據導入,比如從word、excel表格或者txt文檔導入數據(這些數據一般來自於非技術人員通過OFFICE工具錄入的文檔);一類數據交換,比如從MySQL、OracleDB2數據庫之間的數據交換。

這其中就面臨一個問題:數據庫SQL腳本有差異,SQL交換比較麻煩。但是幾乎所有的數據庫都支持文本數據導入(LOAD)導出(EXPORT)功能。利用這一點,就可以解決上面所提到的數據交換和導入問題。

MySQL的LOAD DATAINFILE語句用於高速地從一個文本文件中讀取行,並裝入一個表中。文件名稱必須爲一個文字字符串。下面以MySQL5爲例說明,說明如何使用MySQL的LOADDATA命令實現文本數據的導入。

注意:這裏所說的文本是有一定格式的文本,比如說,文本分行,每行中用相同的符號隔開文本等等。等等,獲取這樣的文本方法也非常的多,比如可以把word、excel表格保存成文本,或者是一個csv文件。

在我的項目中,使用的環境是快速上傳一個csv文件,原系統中是使用的db2數據庫,然後調用了與mysql的loaddata相似的一個函數sysproc.db2load。但是loaddata在mysql的存儲過程是不能使用的。採取的方法時在java代碼中調用此方法。

實現的例子:

準備測試表

SQL如下:

use test;
CREATE TABLE `test` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `a` int(11) NOT NULL,
  `b` bigint(20) unsigned NOT NULL,
  `c` bigint(20) unsigned NOT NULL,
  `d` int(10) unsigned NOT NULL,
  `e` int(10) unsigned NOT NULL,
  `f` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `a_b` (`a`,`b`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

?

Java代碼如下:

package com.seven.dbTools.DBTools;
 
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;
 
import javax.sql.DataSource;
 
import org.apache.log4j.Logger;
 
/**
 * @author seven
 * @since 07.03.2013
 */
public class BulkLoadData2MySQL {
 
    private static final Logger logger = Logger.getLogger(BulkLoadData2MySQL.class);
    private JdbcTemplate jdbcTemplate;
    private Connection conn = null;
 
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
 
    public static InputStream getTestDataInputStream() {
        StringBuilder builder = new StringBuilder();
        for (int i = 1; i <= 10; i++) {
            for (int j = 0; j <= 10000; j++) {
 
                builder.append(4);
                builder.append("\t");
                builder.append(4 + 1);
                builder.append("\t");
                builder.append(4 + 2);
                builder.append("\t");
                builder.append(4 + 3);
                builder.append("\t");
                builder.append(4 + 4);
                builder.append("\t");
                builder.append(4 + 5);
                builder.append("\n");
            }
        }
        byte[] bytes = builder.toString().getBytes();
        InputStream is = new ByteArrayInputStream(bytes);
        return is;
    }
 
    /**
     *
     * load bulk data from InputStream to MySQL
     */
    public int bulkLoadFromInputStream(String loadDataSql,
            InputStream dataStream) throws SQLException {
        if(dataStream==null){
            logger.info("InputStream is null ,No data is imported");
            return 0;
        }
        conn = jdbcTemplate.getDataSource().getConnection();
        PreparedStatement statement = conn.prepareStatement(loadDataSql);
 
        int result = 0;
 
        if (statement.isWrapperFor(com.mysql.jdbc.Statement.class)) {
 
            com.mysql.jdbc.PreparedStatement mysqlStatement = statement
                    .unwrap(com.mysql.jdbc.PreparedStatement.class);
 
            mysqlStatement.setLocalInfileInputStream(dataStream);
            result = mysqlStatement.executeUpdate();
        }
        return result;
    }
 
    public static void main(String[] args) {
        String testSql = "LOAD DATA LOCAL INFILE 'sql.csv' IGNORE INTO TABLE test.test (a,b,c,d,e,f)";
        InputStream dataStream = getTestDataInputStream();
        BulkLoadData2MySQL dao = new BulkLoadData2MySQL();
        try {
            long beginTime=System.currentTimeMillis();
            int rows=dao.bulkLoadFromInputStream(testSql, dataStream);
            long endTime=System.currentTimeMillis();
            logger.info("importing "+rows+" rows data into mysql and cost "+(endTime-beginTime)+" ms!");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        System.exit(1);
    }
 
}
?

提示:

例子中的代碼使用setLocalInfileInputStream方法,會直接忽略掉文件名稱,而直接將IO流導入到數據庫中。在實際的實現中也可以把文件上傳到服務器,然後讀文件再導入文件,此時load data的local參數應該去掉,並且文件名應該是完整的絕對路徑的名字。

最後附上LOAD DATA INFILE語法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
LOAD DATA [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name.txt'
    [REPLACE | IGNORE]
    INTO TABLE tbl_name
    [FIELDS
        [TERMINATED BY 'string']
        [[OPTIONALLY] ENCLOSED BY 'char']
        [ESCAPED BY 'char' ]
    ]
    [LINES
        [STARTING BY 'string']
        [TERMINATED BY 'string']
    ]
    [IGNORE number LINES]
    [(col_name_or_user_var,...)]
    [SET col_name = expr,...)]
  
SELECT語法
    [ALL | DISTINCT | DISTINCTROW ]
      [HIGH_PRIORITY]
      [STRAIGHT_JOIN]
      [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
      [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
    select_expr, ...
    [INTO OUTFILE 'file_name' export_options
      | INTO DUMPFILE 'file_name']
    [FROM table_references
    [WHERE where_definition]
    [GROUP BY {col_name | expr | position}
      [ASC | DESC], ... [WITH ROLLUP]]
    [HAVING where_definition]
    [ORDER BY {col_name | expr | position}
      [ASC | DESC] , ...]
    [LIMIT {[offset,] row_count | row_count OFFSET offset}]
    [PROCEDURE procedure_name(argument_list)]
    [FOR UPDATE | LOCK IN SHARE MODE]]

總結

LOADDATA是一個很有用的命令,從文件中導入數據比insert語句要快,MySQL文檔上說要快20倍左右。但是命令的選項很多,然而大多都用不到,如果真的需要,用的時候看看官方文檔即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章