Java jdbc批量多線程讀取CVS文件入庫(kù)
需求是這樣的:現(xiàn)在需要測(cè)試一個(gè)內(nèi)存數(shù)據(jù)庫(kù)的入庫(kù)性能,要求測(cè)試每線程準(zhǔn)備一個(gè)文件,10個(gè)線程入庫(kù)總計(jì)100w記錄數(shù)的單表入庫(kù)性能。
知識(shí)點(diǎn):jdbc + 多線程 + 批處理 + 文件讀取
先來(lái)看看我的代碼結(jié)構(gòu)

說明:
files: 存放即將要讀取的文件。
lib: 存放第三方的jar文件,例如數(shù)據(jù)庫(kù)驅(qū)動(dòng)包。
MemSqlTestMain: 這是工程的入口,就是主程序。
DBUtil: 這個(gè)類是數(shù)據(jù)庫(kù)幫助類,主要讀取數(shù)據(jù)庫(kù)配置信息獲取連接關(guān)閉連接等操作。
InsertUtil: 主要做的是讀取數(shù)據(jù)文件生成sql并批量入庫(kù)的一個(gè)類。
TableDataInfo: 主要對(duì)要插入的數(shù)據(jù)表的對(duì)象的一個(gè)類。
XMLUtil: 讀取XML配置文件
config.xml: 配置要插入的表信息以及文件的路徑等信息
dbconfig.properties: 主要對(duì)數(shù)據(jù)庫(kù)的連接信息進(jìn)行存儲(chǔ),包括URL,用戶名密碼等等。
話不多說直接上代碼:
import java.util.ArrayList;
/**
* @param
* @author wu.lin
* @description 程序入口,啟用線程讀取文件并入庫(kù)
* @create 2016年09月01日 15:12
* @throws
*/
public class MemSqlTestMain {
public static void main(String[] args) {
//通過讀取配置文件讀取要插入數(shù)據(jù)的表名
String tableName = XMLUtil.getTableName();
System.out.println(tableName);
//通過配置文件讀取數(shù)據(jù)存放的文件的路徑
ArrayList<String> fileNameList = XMLUtil.getFileNameList();
int len = fileNameList.size();
//針對(duì)每一個(gè)文件開啟一個(gè)進(jìn)程去執(zhí)行讀取并入庫(kù)的操作
for (int i = 0; i < len; i++) {
String fileName = fileNameList.get(i);
System.out.println(fileName);
new Thread(new InsertUtil(fileName, tableName)).start();
}
}
}
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.sql.*;
/**
* @param
* @author wu.lin
* @description InsertUtil是一個(gè)線程類,主要讀取數(shù)據(jù)文件組裝Sql并執(zhí)行入庫(kù)操作
* @create 2016年09月01日 14:10
* @throws
*/
public class InsertUtil implements Runnable {
//文件路徑
private String filePath;
//表名
private String tableName;
//.cvs文件數(shù)據(jù)以","分隔
private static String DELIMITERS = ",";
//獲取數(shù)據(jù)庫(kù)幫助類
DBUtil dbutil = DBUtil.getInstance();
public InsertUtil() {}
public InsertUtil(String filePath, String tableName) {
this.filePath = filePath;
this.tableName = tableName;
}
public static String getDELIMITERS() {
return DELIMITERS;
}
public static void setDELIMITERS(String delimiters) {
DELIMITERS = delimiters;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
//讀取文件并且批處理入庫(kù)的方法
public boolean insertDB(String tablename, long rc, String filePath) {
if(filePath == null || "".equals(filePath)) {
System.out.println("文件路徑為空");
return false;
}
if (rc < 1) {
rc = 100;
}
Connection conn = null;
boolean flag = false;
Statement pre = null;
String sql = "";
TableDataInfo tableInfo = new TableDataInfo();
try {
if(conn == null) {
conn = dbutil.getConnection();
}
pre = conn.createStatement();
conn.setAutoCommit(false);
int colCount = tableInfo.getTableColNums(tablename, conn);
int rowCount = 0;
File file = new File(filePath);
BufferedReader buf = null;
buf = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String line_record = buf.readLine();
long startTime = System.currentTimeMillis(); //開始計(jì)時(shí)
while (line_record != null) {
// 解析每一條記錄
sql = "INSERT INTO " + tablename + " VALUES('";
String[] fields = line_record.split(DELIMITERS);
//對(duì)Insert語(yǔ)句的合法性進(jìn)行判斷
if(fields.length != colCount){
System.out.println("要插入的數(shù)據(jù)列數(shù)和表的數(shù)據(jù)列不相匹配,停止執(zhí)行");
break;
}
for (int i = 0; i < fields.length; i++) {
sql += fields[i];
if (i < fields.length - 1) {
sql += "','";
}
}
sql += "');";
// 在控制臺(tái)輸出SQL語(yǔ)句
// System.out.println(sql);
//執(zhí)行SQL語(yǔ)句
pre.addBatch(sql);
rowCount++;
line_record = buf.readLine();
if (rowCount >= rc) {
break;
}
}
pre.executeBatch();
conn.setAutoCommit(true);
pre.close();
System.out.println("共寫入行數(shù):" + rowCount);
long endTime = System.currentTimeMillis(); //停止計(jì)時(shí)
System.out.println("執(zhí)行時(shí)間為:" + (endTime - startTime) + " ms");
} catch (Exception e) {
flag = false;
try {
//回滾
if(conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
dbutil.close(null, pre, conn);
}
return flag;
}
public void run() {
this.insertDB(tableName, 500000, filePath);
}
}
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @param
* @author wu.lin
* @description 數(shù)據(jù)庫(kù)表實(shí)體
* @create 2016年09月01日 14:19
* @throws
*/
public class TableDataInfo {
DBUtil dbutil = DBUtil.getInstance();
/**
*
* @param m_TableName
* @param m_Connection
* @return 該表的列數(shù)
*/
public int getTableColNums(String m_TableName, Connection m_Connection) {
int colCount = 0;
try {
if (m_Connection == null) {
m_Connection = dbutil.getConnection();
}
DatabaseMetaData m_DBMetaData = m_Connection.getMetaData();
ResultSet tableRet = m_DBMetaData.getTables(null, "%", m_TableName,
new String[] { "TABLE" });
while (tableRet.next()) {
System.out.println("Table name is:"
+ tableRet.getString("TABLE_NAME"));
}
String columnName;
String columnType;
ResultSet colRet = m_DBMetaData.getColumns(null, "%", m_TableName,"%");
while (colRet.next()) {
columnName = colRet.getString("COLUMN_NAME");
columnType = colRet.getString("TYPE_NAME");
int dataSize = colRet.getInt("COLUMN_SIZE");
int digits = colRet.getInt("DECIMAL_DIGITS");
int nullable = colRet.getInt("NULLABLE");
String nullFlag;
if (nullable == 1) {
nullFlag = "Null";
} else {
nullFlag = "Not Null";
}
System.out.println(columnName + " " + columnType + "("
+ dataSize + "," + digits + ") " + nullFlag);
colCount++;
}
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println("The number of column is: " + colCount);
return colCount;
}
}
接下來(lái)就剩下讀取配置文件的代碼了,先來(lái)看看配置文件內(nèi)容(這里配置了數(shù)據(jù)庫(kù)配置文件路徑表名以及文件存放的相對(duì)路徑):
<?xml version="1.0" encoding="utf-8" ?> <config> <db_file>src/dbconfig.properties</db_file> <tableName>memtest</tableName> <files> <filePath>files/memtest.csv</filePath> <filePath>files/memtest_1.csv</filePath> <filePath>files/memtest_2.csv</filePath> <filePath>files/memtest_3.csv</filePath> <filePath>files/memtest_4.csv</filePath> <filePath>files/memtest_5.csv</filePath> <filePath>files/memtest_6.csv</filePath> <filePath>files/memtest_7.csv</filePath> <filePath>files/memtest_8.csv</filePath> <filePath>files/memtest_9.csv</filePath> <filePath>files/memtest_10.csv</filePath> </files> </config>
接下來(lái)是讀取這個(gè)配置文件的內(nèi)容,比較簡(jiǎn)單,所以只貼部分代碼:
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;
import java.util.ArrayList;
/**
* @param
* @author wu.lin
* @description 讀取配置信息
* @create 2016年09月01日 15:45
* @throws
*/
public class XMLUtil {
//該方法用于從XML配置文件中提取要插入的表名稱,并返回該表名稱
public static String getTableName() {
return getXmlProperties("tableName");
}
public static String getDatabaseUrl() {
return getXmlProperties("dataBaseUrl");
}
public static String getDbFilePath() {
return getXmlProperties("db_file");
}
private static String getXmlProperties(String proName) {
try {
Document doc = getDoc();
//獲取包含品牌名稱的文本節(jié)點(diǎn)
NodeList nl = doc.getElementsByTagName(proName);
Node classNode=nl.item(0).getFirstChild();
String tableName=classNode.getNodeValue().trim();
return tableName;
} catch(Exception e)
{
e.printStackTrace();
return null;
}
}
private static Document getDoc() throws Exception {
//創(chuàng)建文檔對(duì)象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src/config.xml"));
return doc;
}
}
數(shù)據(jù)庫(kù)配置信息文檔:
db.used=mysql # driver class oracle.jdbc.driver_class=oracle.jdbc.driver.OracleDriver # URL oracle.jdbc.url=jdbc:oracle:thin:@localhost:1521:ORCL # username oracle.jdbc.username=scott # pwd oracle.jdbc.pwd=tiger #mysql connect config mysql.jdbc.driver_class=com.mysql.jdbc.Driver mysql.jdbc.url=jdbc:mysql://localhost:3306/mysqldb mysql.jdbc.username=root mysql.jdbc.pwd=
最后是數(shù)據(jù)庫(kù)幫助類,比較常見:
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @param
* @author wu.lin
* @description 數(shù)據(jù)庫(kù)幫助類
* @create 2016年09月01日 18:56
* @throws
*/
public class DBUtil {
private static Properties env = new Properties();
private static DBUtil dbutil;
private static String dbname;
private static String driverClass_key;
private static String url_key;
private static String username_key;
private static String pwd_key;
private DBUtil(){}
// 單例模式
public static synchronized DBUtil getInstance() {
if (null == dbutil) {
dbutil = new DBUtil();
}
return dbutil;
}
/**
* 得到數(shù)據(jù)庫(kù)連接
* @return
*/
public Connection getConnection() {
Connection conn = null;
try {
env.load(new FileInputStream(XMLUtil.getDbFilePath()));
dbname = env.getProperty("db.used").toLowerCase();
driverClass_key = dbname + ".jdbc.driver_class";
url_key = dbname + ".jdbc.url";
username_key = dbname + ".jdbc.username";
pwd_key = dbname + ".jdbc.pwd";
//加載連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng)程序類文件
Class.forName(env.getProperty(driverClass_key));
conn = createConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
private Connection createConnection() throws SQLException {
Connection conn = null;
if ("oracle".equals(dbname)) {
conn = DriverManager.getConnection(env.getProperty(url_key), env.getProperty(username_key),
env.getProperty(pwd_key));
}
if ("sqlserver".equals(dbname)) {
conn = DriverManager.getConnection(env.getProperty(url_key), env.getProperty(username_key),
env.getProperty(pwd_key));
}
if ("mysql".equals(dbname)) {
// 其他數(shù)據(jù)庫(kù)的連接語(yǔ)法
String url = env.getProperty(url_key);
String username = env.getProperty(username_key);
String pwd = env.getProperty(pwd_key);
if(username != null && !"".equals(username)) {
url += ("?user=" + username);
if(pwd != null && !"".equals(pwd)) {
url += ("&password=" + pwd);
}
}
conn = DriverManager.getConnection(url);
}
return conn;
}
//提供jdbc關(guān)閉連接的方法
public void close(ResultSet rs,Statement st,Connection conn){
try {
if(rs!=null)
rs.close();
if(st!=null)
st.close();
if(conn!=null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
最后的工作便是在文件目錄存放相應(yīng)的數(shù)據(jù)文件,然后通過配置文件配置好文件名、表名以及數(shù)據(jù)庫(kù)連接的基本信息后,運(yùn)行程序入口,便可以將程序跑起來(lái)啦。但是在這個(gè)過程中也遇到一些小問題,比如,我這邊只有一個(gè)100w條數(shù)據(jù)的.csv格式的文件,但是要求讀取十個(gè)文件,在這個(gè)時(shí)候我用到了一個(gè)小工具:

大家知道.csv格式的文件也可以用Excel軟件打開,所以在這里轉(zhuǎn)換一下用Excel分割器把文件分成十份,就完美的解決問題啦。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于Springboot+Mybatis對(duì)數(shù)據(jù)訪問層進(jìn)行單元測(cè)試的方式分享
本文將介紹一種快高效、可復(fù)用的解決測(cè)試方案——對(duì)數(shù)據(jù)訪問層做單元測(cè)試,文章通過代碼示例介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2023-07-07
Spring 中jdbcTemplate 實(shí)現(xiàn)執(zhí)行多條sql語(yǔ)句示例
本篇文章主要介紹了Spring 中jdbcTemplate 實(shí)現(xiàn)執(zhí)行多條sql語(yǔ)句示例,可以對(duì)多個(gè)表執(zhí)行多個(gè)sql語(yǔ)句,有興趣的可以了解一下。2017-01-01
使用java反射將結(jié)果集封裝成為對(duì)象和對(duì)象集合操作
這篇文章主要介紹了使用java反射將結(jié)果集封裝成為對(duì)象和對(duì)象集合操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2020-08-08
如何解決SpringBoot定時(shí)任務(wù)報(bào)錯(cuò)Unexpected error occurred 
這篇文章主要介紹了如何解決SpringBoot定時(shí)任務(wù)報(bào)錯(cuò)Unexpected error occurred in scheduled task問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Spring與Struts整合之讓Spring管理控制器操作示例
這篇文章主要介紹了Spring與Struts整合之讓Spring管理控制器操作,結(jié)合實(shí)例形式詳細(xì)分析了Spring管理控制器相關(guān)配置、接口實(shí)現(xiàn)與使用技巧,需要的朋友可以參考下2020-01-01
java 中List按照date排序的實(shí)現(xiàn)
這篇文章主要介紹了java 中List按照date排序的實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-06-06

