标题:源生jdbc备份数据库数据生成dat文件,多线程批量入库
*1.获取jdbc连接
方法1:初始化加载spring框架,获取jdbc连接
/通过配置文件得到ali数据源的连接/
public Connection getAlibabaDruidConnection(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{“spring/applicationContext.xml”});
context.start();
logger.info(“spring start done …”);
DruidDataSource dataSource = (dataSource) SpringBeanUtil.getBean(“dataSource_default”);
Connection connection = null;
try {
connection = druidDataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
主要是读取数据的dao层配置信息,获取连接。
方法2:得到本地数据库连接
备注:区别就在于获取连接时一个传入的参数是自己手写,ali的自己封装了通过配置文件读取的。
/得到本地数据库的连接/
public Connection getLocalConnection(String url, String dataBase, String user, String pwd){
Connection connection = null;
try{
Class.forName(“com.mysql.jdbc.Driver”);
String url_total = url+"/"+dataBase+url_unic;
connection = DriverManager.getConnection(url_total,user,pwd);
logger.info(“本地数据库已成功连接!”);
}catch (Exception e){
logger.info(“本地数据库连接失败!”);
e.printStackTrace();
}
return connection;
}
2.通过获取的ResultSet集备份数据到当地磁盘
1.根据主键ID取模存取不同的文件。
利用缓冲流写入文件:BufferedWriter缓冲流提高效率
存在的问题就是文件存在不用重建,文件不存在就要建立文件,解决方法:
Map<Integer,BufferedWriter> allFileWriter=new HashMap<Integer,BufferedWriter>();
int Mod=Math.abs(ID.intValue()%32);
int Mod=Math.abs(ACCT_ID.intValue()%32);
while(resultSet.next()){
oriRecords++;
//这里可以在外面定义一个变量用记录备份了多少条数据
if(!allFileWriter.containsKey(Mod)){
String fileName =“E:\BILLCENTER/”+“20C_”+(Math.abs(ACCT_ID.intValue()%32))+ “.dat”;
File file = new File(fileName);
file.createNewFile();
BufferedWriter writer = new BufferedWriter(new PrintWriter(file,“UTF-8”));
allFileWriter.put(Mod,writer);
}
BufferedWriter curWriter= allFileWriter.get(Mod);
缓冲流没写完一次flush()一次,最后finally语句块关闭所有句柄
for(Map.Entry<Integer,BufferedWriter> curWriter:allFileWriter.entrySet()){
curWriter.getValue().close();
}
}
然后备份成功。
3.读取当地磁盘文件,多线程批量入库
1.从磁盘扫描所有的文件,放入队列
LinkedBlockingQueue fileQueue = new LinkedBlockingQueue(32);
fileQueue.put(fileNamelist);
2.开多线程处理队列中的文件
public void handle(int threadNum){
//开多线程处理队列中的文件
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threadNum);
for(int i=0;i<threadNum;i++){
if(getFileQueue() == null){
break;
}else {
fixedThreadPool.execute(new InsertThread(getFileQueue()));
}
}
fixedThreadPool.shutdown();
while (!fixedThreadPool.isTerminated()){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3. InsertThread里run方法具体实现:
while(true){
PreparedStatement pstmt = null;
String path=fileQueue.poll();
if(pathnull){
//队列为空了,则线程退出
break;
}
//TODO读取文件,入库
File file = new File(path);
InputStreamReader isr = new InputStreamReader(new FileInputStream(file), “utf8”);
BufferedReader bw = new BufferedReader(isr);
String line = null;
connection =
DBUtil.getInstance().getLocalConnection(“jdbc:mysql://localhost:3306”,“acctbd”,“root”,“root”);
String sql = “replace into " + “acct_item_20c” + " (” + “ACCT_ITEM_ID,ACCT_ID,ACCT_ITEM_TYPE_ID,AMOUNT,BILL_ID,BILLING_CYCLE_ID,” +
“CREATE_DATE,CUST_ID,FEE_CYCLE_ID,GRP_ACCT_ITEM_TYPE_NBR,HAD_INVOICE_AMOUNT,ITEM_SOURCE_ID,NO_INVOICE_AMOUNT,OFFER_INST_ID” +
“,ORI_ACCT_ITEM_ID,ONE_ACCT_ITEM_ID,PAY_CYCLE_ID,PAYMENT_METHOD,PRESENT_AMOUNT,PROD_INST_ID,STATUS_CD,STATUS_DATE,REGION_ID,” +
“PARTNER_ID,BILL_XCHG_ID,PRD_ID,OFFER_ID,UNSURE_INCOME,EVENT_PRICING_STRATEGY_ID,LATN_ID,PLATFORM,card_flag)” + " values " +
“(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)”;
pstmt = connection.prepareStatement(sql);
//这两个变量作为批量入库的判定标识
int idx=1;
int leftIdx=0;
while((line = bw.readLine()) != null){
if(“null”.equals(arr[4]) || StringUtils.isBlank(arr[4])){
pstmt.setNull(5,Types.DECIMAL);
}else {
pstmt.setBigDecimal(5,new BigDecimal(arr[4]));
}
…
pstmt.addBatch();
//N条提交一次
if(idx%10000){
pstmt.executeBatch();
//删除批次
pstmt.clearBatch();
//connection.commit();
//剩余量设置为0
leftIdx=0;
}else{
leftIdx++;
}
idx++;
}
//最后再做一次检测
if (leftIdx>0){
pstmt.executeBatch();
//删除批次
pstmt.clearBatch();
//connection.commit();
}
bw.close();
}
}
这里涉及到几个问题要注意下:
1.两个过程可以同时进行,边读边写提升效率。
2. 源生的jdbc塞一个null值,不是空,是一个为字符串的null值。还有字段设置为可以为null值的字段,要进行判断,要严谨。
if(“null”.equals(arr[4]) || StringUtils.isBlank(arr[4])){
pstmt.setNull(5,Types.DECIMAL);
}else {
pstmt.setBigDecimal(5,new BigDecimal(arr[4]));
}
3.批量提交的时候,要考虑每一种发生的情况,使性能最大化。
4.插库如表的时候 replace into 用于这个表的主键有自增。还有Ingore 方法也可以具体没试。
5.这里如果想知道成功插入了多少条,异常了多少条。方法具体如下:
public AtomicLong dataRecord;定义一个AtomicLong类型的变量,
while((line = bw.readLine()) != null){
dataRecord20C.incrementAndGet();
添加数据方法…
}
AtomicLong dataRecord20C=new AtomicLong();
AtomicLong dataRecord20P=new AtomicLong();
AtomicLong dataRecordBalance=new AtomicLong();
long startInsert=System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(insertThreadNum);
for(int i=0;i<insertThreadNum;i++){
fixedThreadPool.execute(new InsertThread(fileQueue, latnId, billingCycle,dataRecord20C,dataRecord20P,dataRecordBalance));
}
fixedThreadPool.shutdown();
while (!fixedThreadPool.isTerminated()){
try {
Thread.sleep(1000*60);
logger.info("has insert 20C RECORDS [{}],20P RECORDS [{}],BALANCE RECORDS [{}]",dataRecord20C,dataRecord20P,dataRecordBalance);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
6.读取数据库的数据时,如果数据量很多千万级别的,千万注意防止GC挂机问题。因为数据量过大,resultSet集肯定很大,所以要给予一定的限制,可以加上这几句。
preparedStatement = conn.prepareStatement(sql,resultSet.TYPE_FORWARD_ONLY,resultSet.CONCUR_READ_ONLY);
//执行sql前,添加这两句
preparedStatement.setFetchSize(Integer.MIN_VALUE);
preparedStatement.setFetchDirection(ResultSet.FETCH_REVERSE);
resultSet = preparedStatement.executeQuery();
Ok Fine ~