删除程序之一,用于删除id=1的一条记录,在删除后停住断点,另一个删除程序会进行不下去。
package com.hy.multidelete; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import org.apache.log4j.Logger; public class Deleter { private static Logger log = Logger.getLogger(Deleter.class); public void doDelete() { Connection conn = null; Statement stmt = null; try{ Class.forName(DBParam.Driver).newInstance(); conn = DriverManager.getConnection(DBParam.DbUrl, DBParam.User, DBParam.Pswd); conn.setAutoCommit(false); stmt = conn.createStatement(); String sql="delete from TestTB17 where id=1"; int deleted=stmt.executeUpdate(sql); // 在此处停住断点,另一个删除程序CleanExpiredMocker会执行不下去 log.info("Deleter deleted "+deleted+" records."); // 直到接下来回滚或提交CleanExpiredMocker才可以执行 conn.rollback(); log.info("Rollbacked."); } catch (Exception e) { System.out.print(e.getMessage()); } finally { try { stmt.close(); conn.close(); } catch (SQLException e) { System.out.print("Can't close stmt/conn because of " + e.getMessage()); } } } public static void main(String[] args) { Deleter d=new Deleter(); d.doDelete(); } }
另一删除程序,用于删除整表全部记录记录,此删除与上一程序删除id=1的操作存在交集,因此如果deleter先执行,要等deleter提交或回滚后,此程序才能执行下去:
package com.hy.multidelete; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import org.apache.log4j.Logger; public class CleanExpiredMocker { private static Logger log = Logger.getLogger(CleanExpiredMocker.class); public void doClean() { Connection conn = null; Statement stmt = null; try{ Class.forName(DBParam.Driver).newInstance(); conn = DriverManager.getConnection(DBParam.DbUrl, DBParam.User, DBParam.Pswd); stmt = conn.createStatement(); String sql="delete from TestTB17 where 1=1"; int deleted=stmt.executeUpdate(sql); log.info("CleanExpiredMocker deleted "+deleted+" records.");; } catch (Exception e) { System.out.print(e.getMessage()); } finally { try { stmt.close(); conn.close(); } catch (SQLException e) { System.out.print("Can't close stmt/conn because of " + e.getMessage()); } } } public static void main(String[] args) { CleanExpiredMocker c=new CleanExpiredMocker(); c.doClean(); } }
Select ...for update 也有同样阻断其它session(操作记录与select...for update语句的结果集存在交集)的效果:
package tablelock; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import org.apache.log4j.Logger; public class Selecter { private static Logger log = Logger.getLogger(Selecter.class); public void doDelete() { Connection conn = null; Statement stmt = null; try{ Class.forName(DBParam.Driver).newInstance(); conn = DriverManager.getConnection(DBParam.DbUrl, DBParam.User, DBParam.Pswd); conn.setAutoCommit(false); stmt = conn.createStatement(); String sql="select * from TestTB17 for update"; stmt.executeUpdate(sql); log.info("Will block other session before commit/rollback."); conn.rollback(); log.info("Rollbacked."); } catch (Exception e) { System.out.print(e.getMessage()); } finally { try { stmt.close(); conn.close(); } catch (SQLException e) { System.out.print("Can't close stmt/conn because of " + e.getMessage()); } } } public static void main(String[] args) { Selecter d=new Selecter(); d.doDelete(); } }
往下是一些数据准备工作,可以一笑而过。
建表程序:
package com.hy.multidelete; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.apache.log4j.Logger; /** * 此类用于管理表的创建和销毁 * @author 逆火 * * 2019年11月16日 下午5:40:22 */ public class TableHandler { private static Logger log = Logger.getLogger(TableHandler.class); private String[] tablenames= { "TestTB17", }; /** * Get the create table ddl of a table * @param table * @return */ private String getCreateTbSql(String table) { StringBuilder sb=new StringBuilder(); sb.append("CREATE TABLE "+table); sb.append("("); sb.append("\"ID\" NUMBER(8,0) not null primary key,"); sb.append("\"NAME\" NVARCHAR2(60) not null,"); sb.append("\"AGE\" NUMBER(3,0) DEFAULT 0 not null ,"); sb.append("\"CREATEDTIME\" TIMESTAMP (6) not null"); sb.append(")"); return sb.toString(); } /** * Judge if a table is exist * @param table * @param stmt * @return * @throws SQLException */ private boolean isTableExist(String table,Statement stmt) throws SQLException { String sql="SELECT COUNT (*) as cnt FROM ALL_TABLES WHERE table_name = UPPER('"+table+"')"; ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { int count = rs.getInt("cnt"); return count==1; } return false; } /** * Crate tables */ public void createTables() { Connection conn = null; Statement stmt = null; try{ Class.forName(DBParam.Driver).newInstance(); conn = DriverManager.getConnection(DBParam.DbUrl, DBParam.User, DBParam.Pswd); stmt = conn.createStatement(); int i=0; for(String table:tablenames) { i++; String sql=getCreateTbSql(table); stmt.executeUpdate(sql); if(isTableExist(table,stmt)) { log.info("#"+i+" "+table+" created."); } } } catch (Exception e) { System.out.print(e.getMessage()); } finally { try { stmt.close(); conn.close(); } catch (SQLException e) { System.out.print("Can't close stmt/conn because of " + e.getMessage()); } } } /** * Remove all the tables */ public void dropTables() { Connection conn = null; Statement stmt = null; try{ Class.forName(DBParam.Driver).newInstance(); conn = DriverManager.getConnection(DBParam.DbUrl, DBParam.User, DBParam.Pswd); stmt = conn.createStatement(); int i=0; for(String table:tablenames) { i++; String sql="drop table "+table; stmt.executeUpdate(sql); if(isTableExist(table,stmt)==false) { log.info("#"+i+" "+table+" dropped."); } } } catch (Exception e) { System.out.print(e.getMessage()); } finally { try { stmt.close(); conn.close(); } catch (SQLException e) { System.out.print("Can't close stmt/conn because of " + e.getMessage()); } } } /** * Kick start * @param args */ public static void main(String[] args) { TableHandler th=new TableHandler(); th.createTables(); } }
给表插入五百记录的程序:
package com.hy.multidelete; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import org.apache.log4j.Logger; import com.hy.DBParam; class TbCnt{ String tb;// tablename int cnt;// count should be inserted public TbCnt(String tb,int cnt) { this.tb=tb; this.cnt=cnt; } } class Field{ String name; // 列名 int size; // 列容量 String type; // 列数据类型 boolean allowNull;// 列是否允許为空 } /** * 输入表名和需要插入的记录数,自动将所需记录插入表,不需要指定主键和必填字段 * @author 逆火 * * 2019年11月23日 下午2:19:02 */ public class TableAutoInserter { private static Logger log = Logger.getLogger(TableAutoInserter.class); private final int BatchSize=250;// 一次性插入记录数 private List<TbCnt> tbCntList;// 容纳表名和插入数量的链表 /** * 构造函数 */ public TableAutoInserter() { tbCntList=new ArrayList<TbCnt>(); } /** * 加入一张要插入的表 * @param tc * @return */ public TableAutoInserter add(TbCnt tc) { tbCntList.add(tc); return this; } /** * 给已有的所有表插值 */ public void insertAllTables() { Connection conn = null; Statement stmt = null; try{ Class.forName(DBParam.Driver).newInstance(); conn = DriverManager.getConnection(DBParam.DbUrl, DBParam.User, DBParam.Pswd); stmt = conn.createStatement(); log.info("Begin to access "+DBParam.DbUrl+" as "+DBParam.User+"..."); int index=0; for(TbCnt tc:tbCntList) { String table=tc.tb; int count=tc.cnt; long startTime = System.currentTimeMillis(); truncateTable(table,conn,stmt); List<TypeField> fieldList=fetchTableFields(conn,stmt,table,false); insertDataToTable(index,table,count,fieldList,conn,stmt); if(isAllInserted(count,table,stmt)) { long endTime = System.currentTimeMillis(); log.info("#"+index+" "+count+" records were inserted to table:'" + table + "' used " + sec2DHMS(startTime,endTime) ); } index++; } } catch (Exception e) { System.out.print(e.getMessage()); e.printStackTrace(); } finally { try { stmt.close(); conn.close(); } catch (SQLException e) { log.error("Can't close stmt/conn because of " + e.getMessage()); } } } /** * 从表中取字段信息 * @param conn * @param stmt * @param table * @param isFetchall:此参数为真时取所有字段,为假时取非空字段 * @return * @throws Exception */ private List<TypeField> fetchTableFields(Connection conn,Statement stmt,String table,boolean isFetchall) throws SQLException{ List<TypeField> fields=new ArrayList<TypeField>(); String sql = "select * from "+table+""; ResultSet rs = stmt.executeQuery(sql); ResultSetMetaData rsMetadata = rs.getMetaData(); int count = rsMetadata.getColumnCount(); for (int i=0; i<count; i++) { int index=i+1; String columnName = rsMetadata.getColumnLabel(index);// 列名 String columnType = rsMetadata.getColumnTypeName(index);// 列数据类型 boolean allowNull=(rsMetadata.isNullable(index)!=0);// 列是否允許为空 //int size =rsMetadata.getColumnDisplaySize(index);// 列容量 此值在数据超过列容量限制时能用到 TypeField tf=new TypeField(); tf.field=columnName; tf.type=getShortType(columnName,columnType); if(isFetchall==true) { fields.add(tf); }else { if(allowNull==false) { fields.add(tf); } } } rs.close(); return fields; } /** * 得到列类型的短名称,此短名称在getInsertSql函数中会用到 * @param columnType * @return * @throws SQLException */ private String getShortType(String field,String columnType) throws SQLException{ if(field.equalsIgnoreCase("id")) { return "PK"; } if(columnType.equalsIgnoreCase("timestamp")) { return "DT"; }else if(columnType.equalsIgnoreCase("number")) { return "NB"; }else if(columnType.equalsIgnoreCase("nvarchar2")) { return "CH"; }else { throw new SQLException("Unknown columnType:'"+columnType+"'."); } } /** * delete all data in a table quickly * @param tableName * @param conn * @param stmt * @throws SQLException */ private void truncateTable(String tableName,Connection conn,Statement stmt) throws SQLException{ String sql="truncate table "+tableName; stmt.execute(sql); log.info("truncated table:"+tableName); } /** * Insert date to a table * @param tableIndex * @param tableName * @param count * @param innerArr * @param conn * @param stmt * @throws SQLException */ private void insertDataToTable(int tableIndex,String tableName,int count,List<TypeField> typefields,Connection conn,Statement stmt) throws SQLException{ List<String> fields=new ArrayList<String>(); List<String> values=new ArrayList<String>(); int index=0; for(TypeField tf:typefields) { fields.add(tf.field); values.add("''{"+index+"}''"); index++; } int interval=2*365*24*60*60/count;// 涓ゅ勾鐨勭鏁伴櫎浠ユ�讳釜鏁板嵆涓洪棿闅� index=0; int times=count/BatchSize; for(int i=0;i<times;i++) { StringBuilder sb=new StringBuilder(); sb.append("INSERT ALL "); for(int j=0;j<BatchSize;j++) { index=i*BatchSize+j; sb.append(getInsertSql(tableName,typefields,index,interval)); } sb.append(" select * from dual"); String sql = sb.toString(); long startTime = System.currentTimeMillis(); stmt.executeUpdate(sql); long endTime = System.currentTimeMillis(); log.info("#"+tableIndex+"-"+i+" "+BatchSize+" records inserted to '"+tableName+"' used " + sec2DHMS(startTime,endTime)); } } /** * get insert sql * @param tableName * @param typefields * @param index * @return */ private String getInsertSql(String tableName,List<TypeField> typefields,int index,int interval) { String currTime=getDatetimeBefore(index,interval); StringBuilder sb=new StringBuilder(); sb.append(" INTO "+tableName+"("); List<String> fields=new ArrayList<String>(); for(TypeField tf:typefields) { fields.add(tf.field); } sb.append(String.join(",",fields)); sb.append(") values("); List<String> values=new ArrayList<String>(); for(TypeField tf:typefields) { if(tf.type.equals("PK")) { values.add("'"+String.valueOf(index)+"'"); }else if(tf.type.equals("CH")) { values.add("'0'"); }else if(tf.type.equals("NB")) { values.add("'0'"); }else if(tf.type.equals("DT")) { values.add("to_date('"+currTime+"','yyyy-MM-dd HH24:mi:ss')"); } } sb.append(String.join(",",values)); sb.append(")"); String insertSql=sb.toString(); return insertSql; } /** * get datetime n seconds before * @param n * @param interval * @return */ private static String getDatetimeBefore(int n,int interval) { try { Calendar now = Calendar.getInstance(); now.add(Calendar.SECOND,-n*interval);//鏃ユ湡鍑忓幓n*10绉� Date newDate=now.getTime(); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String retval = sdf.format(newDate); return retval; } catch(Exception ex) { ex.printStackTrace(); return null; } } /** * judge if all records are inserted * @param count * @param table * @param stmt * @return * @throws SQLException */ private boolean isAllInserted(int count,String table,Statement stmt) throws SQLException { String sql="SELECT COUNT (*) as cnt FROM "+table; ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { int cnt = rs.getInt("cnt"); return cnt==count; } return false; } /** * change seconds to DayHourMinuteSecond format * @param stratMs * @param endMs * @return */ private static String sec2DHMS(long stratMs,long endMs) { String retval = null; long secondCount=(endMs-stratMs)/1000; long days = secondCount / (60 * 60 * 24); long hours = (secondCount % (60 * 60 * 24)) / (60 * 60); long minutes = (secondCount % (60 * 60)) / 60; long seconds = secondCount % 60; if (days > 0) { retval = days + "d" + hours + "h" + minutes + "m" + seconds + "s"; } else if (hours > 0) { retval = hours + "h" + minutes + "m" + seconds + "s"; } else if (minutes > 0) { retval = minutes + "m" + seconds + "s"; } else { retval = seconds + "s"; } return retval; } protected static final class TypeField{ String type; String field; } public static void main(String[] args) { TableAutoInserter ti=new TableAutoInserter(); String[] tablenames= { "TestTB17",}; for(String table:tablenames) { ti.add(new TbCnt(table,500)); } ti.insertAllTables(); } }
Oracle数据库的连接参数:
package com.hy.multidelete; /** * 数据库连接参数 * @author 逆火 * * 2019年11月16日 上午8:09:24 */ public final class DBParam { public final static String Driver = "oracle.jdbc.driver.OracleDriver"; public final static String DbUrl = "jdbc:oracle:thin:@127.0.0.1:1521:orcl"; public final static String User = "ufo"; public final static String Pswd = "1234"; }
--END-- 2019年11月28日20:53:39