【java线程】新版基于生产者队列消费者的表迁移器
前作:
https://www.cnblogs.com/heyang78/p/16477819.html
【代码下载地址】
https://files.cnblogs.com/files/heyang78/brandnewcpmgrt_20220717.rar?t=1658015436
【主要改进点】
1.原来的DataQueue是手写的,实际上使用java提供的java.util.concurrent.BlockingQueue就完全够了,故减少一个类;
2.Worker更名为Carrier,名字更贴切;
3.DstWriter启动Carrier的方式从直接new线程改为线程池启动。
【代码】
数据库连接类
package com.hy.lab.brandnewcpmgrt; import java.sql.Connection; import java.sql.DriverManager; /** * 连Oracle数据库,提供连接的工具类 */ class DbUtil { //-- 以下为连接Oracle数据库的四大参数 private static final String DRIVER = "oracle.jdbc.OracleDriver"; private static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521/orcl"; private static final String USER = "luna"; private static final String PSWD = "1234"; public static Connection getConn() throws Exception{ Class.forName(DRIVER); Connection conn = DriverManager.getConnection(URL, USER, PSWD); return conn; } }
迁移器类
package com.hy.lab.brandnewcpmgrt; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; /** * 基于消费者队列生产者模型的迁移器 */ class Migrater { // 成员变量:来源连接 private Connection srcConn; // 成员变量:来源表 private String srcTb; // 成员变量:去向连接 private Connection dstConn; // 成员变量:去向表 private String dstTb; // 批处理大小,可灵活调整 private final int BATCH_SIZE=10000; // 内含写去向表工人数量,写完后srcReader会关闭来源去向连接 private CountDownLatch cdl; /** * 构造函数 * * @param srcConn 来源连接 * @param srcTb 来源表 * @param dstConn 去向连接 * @param dstTb 去向表 */ public Migrater(Connection srcConn, String srcTb, Connection dstConn, String dstTb) { this.srcConn=srcConn; this.srcTb=srcTb; this.dstConn=dstConn; this.dstTb=dstTb; // 计算写去向表工人数量 int workerCnt=(int)Math.ceil((double)getSrcCount()/(double)BATCH_SIZE); cdl=new CountDownLatch(workerCnt); } private int getSrcCount(){ final String sql=String.format("select count(*)from %s",srcTb); try(PreparedStatement ps=srcConn.prepareStatement(sql); ResultSet rs=ps.executeQuery()){ while(rs.next()){ return rs.getInt(1); } return -1; }catch(Exception e){ return -2; } } /** * 迁移 */ public void migrate(){ BlockingQueue<List<String[]>> dq=new ArrayBlockingQueue<>(5); SrcReader readerThread=new SrcReader(this,dq,BATCH_SIZE); readerThread.start(); DstWriter writeThread=new DstWriter(this,dq,readerThread); writeThread.start(); } public String getQuerySql(){ return String.format("select * from %s",this.srcTb); } public void transfer(List<String[]> rowList) throws Exception{ Connection conn=this.dstConn; conn.setAutoCommit(false); final String insertSql=String.format("insert into %s(id,f1,f2,f3,f4,f5,f6,f7,f8,f9) values(?,?,?,?,?,?,?,?,?,?)",this.dstTb); try(PreparedStatement pstmt=this.srcConn.prepareStatement(insertSql)){ for(String[] arr:rowList){ for(int i=0;i<arr.length;i++){ String cellValue=arr[i]; pstmt.setString(i+1,cellValue); } pstmt.addBatch(); } pstmt.executeBatch(); conn.commit(); pstmt.clearBatch(); }catch(Exception e){ e.printStackTrace(); } } public Connection getSrcConn() { return srcConn; } public String getSrcTb() { return srcTb; } public Connection getDstConn() { return dstConn; } public String getDstTb() { return dstTb; } public CountDownLatch getCdl() { return cdl; } public static void main(String[] args){ try{ Connection srcConn= DbUtil.getConn(); Connection dstConn= DbUtil.getConn(); // 设定源表存在有数据,去向表存在无数据,两表表结构一致 Migrater mgrt=new Migrater(srcConn,"emp625_from",dstConn,"emp625_to"); mgrt.migrate(); System.out.println("迁移开始"); }catch(Exception ex){ ex.printStackTrace(); } } }
源端读取类
package com.hy.lab.brandnewcpmgrt; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; /** * Source Reader * 从源端读取数据的生产者 */ class SrcReader extends Thread{ Migrater mgrt; BlockingQueue<List<String[]>> queue; int batchSize; public SrcReader(Migrater mgrt, BlockingQueue<List<String[]>> queue, int batchSize){ this.mgrt=mgrt; this.queue=queue; this.batchSize=batchSize; } public void run(){ final String sql=mgrt.getQuerySql(); try(PreparedStatement pstmt=mgrt.getSrcConn().prepareStatement(sql); ResultSet rs=pstmt.executeQuery();){ final int colCnt=10;// 假定列数为10,实际上要用Metadata取 List<String[]> rowList=new ArrayList<>(batchSize); int count=0; while(rs.next()){ String[] arr=new String[colCnt]; for(int i=0;i<colCnt;i++){ arr[i]=rs.getString(i+1); } rowList.add(arr); count++; if(count==batchSize){ queue.put(rowList); count=0; rowList=new ArrayList<>(batchSize); } } rs.close(); if(rowList.size()>0){ queue.put(rowList); } mgrt.getCdl().await(10, TimeUnit.SECONDS); List<String[]> endLs=new ArrayList<>(); endLs.add(null); queue.put(endLs); System.out.println("SrcReader任务完成"); mgrt.getSrcConn().close(); mgrt.getDstConn().close(); }catch(Exception ex){ ex.printStackTrace(); } } }
目标端写入类
package com.hy.lab.brandnewcpmgrt; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * Destination Writer * 用于启动工人向目标端写数据 */ class DstWriter extends Thread{ Migrater mgrt; BlockingQueue<List<String[]>> queue; SrcReader srcReader; public DstWriter(Migrater mgrt, BlockingQueue<List<String[]>> queue, SrcReader srcReader){ this.mgrt=mgrt; this.queue=queue; this.srcReader=srcReader; } public void run(){ try{ Executor exec= Executors.newFixedThreadPool(4); int cnt=0; while(true){ cnt++; List<String[]> rowList=queue.take(); String[] arr=rowList.get(0); if(arr!=null) { //new Thread(new Carrier(mgrt, rowList)).start(); exec.execute(new Carrier(mgrt, rowList)); Thread.sleep(10); System.out.println(String.format("DstWriter运作第%d次",cnt)); }else{ break; } } System.out.println("DstWriter任务完成"); }catch(Exception ex){ ex.printStackTrace(); } } }
挑夫类
package com.hy.lab.brandnewcpmgrt; import java.util.List; import java.util.concurrent.CountDownLatch; /** * 实际向目标端写数据的挑夫 * 由DstWriter产生 */ class Carrier implements Runnable{ Migrater mgrt; List<String[]> rowList; CountDownLatch cdl; public Carrier(Migrater mgrt, List<String[]> rowList){ this.mgrt=mgrt; this.rowList=rowList; this.cdl=mgrt.getCdl(); } @Override public void run() { try{ mgrt.transfer(rowList); rowList=null; }catch(Exception ex){ ex.printStackTrace(); }finally { cdl.countDown(); } } }
【输出】
迁移开始
DstWriter运作第1次
DstWriter运作第2次
DstWriter运作第3次
DstWriter运作第4次
DstWriter运作第5次
DstWriter运作第6次
DstWriter运作第7次
DstWriter运作第8次
DstWriter运作第9次
DstWriter运作第10次
DstWriter运作第11次
DstWriter运作第12次
DstWriter运作第13次
DstWriter运作第14次
DstWriter运作第15次
DstWriter运作第16次
DstWriter运作第17次
DstWriter运作第18次
DstWriter运作第19次
DstWriter运作第20次
DstWriter运作第21次
DstWriter运作第22次
DstWriter运作第23次
DstWriter运作第24次
DstWriter运作第25次
DstWriter运作第26次
DstWriter运作第27次
DstWriter运作第28次
DstWriter运作第29次
DstWriter运作第30次
DstWriter运作第31次
DstWriter运作第32次
DstWriter运作第33次
DstWriter运作第34次
DstWriter运作第35次
DstWriter运作第36次
DstWriter运作第37次
DstWriter运作第38次
DstWriter运作第39次
DstWriter运作第40次
DstWriter运作第41次
DstWriter运作第42次
DstWriter运作第43次
DstWriter运作第44次
DstWriter运作第45次
DstWriter运作第46次
DstWriter运作第47次
DstWriter运作第48次
DstWriter运作第49次
DstWriter运作第50次
DstWriter运作第51次
SrcReader任务完成
DstWriter任务完成
【数据库情况】
luna@ORCL>select count(*) from emp625_to; COUNT(*) ---------- 500012 已选择 1 行。
【程序涉及到的表和充值语句】
create table emp625_from( id number(10), f1 nvarchar2(10), f2 nvarchar2(10), f3 nvarchar2(10), f4 nvarchar2(10), f5 nvarchar2(10), f6 nvarchar2(10), f7 nvarchar2(10), f8 nvarchar2(10), f9 nvarchar2(10), primary key(id) ); create table emp625_to( id number(10), f1 nvarchar2(10), f2 nvarchar2(10), f3 nvarchar2(10), f4 nvarchar2(10), f5 nvarchar2(10), f6 nvarchar2(10), f7 nvarchar2(10), f8 nvarchar2(10), f9 nvarchar2(10), primary key(id) ); insert into emp625_from select rownum, dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)) from dual connect by level<500013;
END