關於事物的概念及原理有好多好多,但我的理解就是一句話,要保持一個整體性,就是一個事,要分成幾個步驟去完成,但是只有這幾個步驟都做完了才是一個完整的事。這裡面最簡單的示例就是模擬銀行的取款與存款了,我直接給出這個示例代碼然後加以分析
1 @Test 2 public void test2(){ 3 String payId="00001"; 4 String recId="00002"; 5 double mny=1000; 6 Connection conn=null; 7 try { 8 conn=DBUtil.getConnection(); 9 String sql="select *from accounts_jiawen " 10 + "where id=?"; 11 PreparedStatement ps=conn.prepareStatement(sql); 12 ps.setString(1, recId); 13 ResultSet rs=ps.executeQuery(); 14 if(!rs.next()){ 15 System.out.println("收款帳號不存在"); 16 throw new SQLException("收款帳號不存在"); 17 } 18 //記錄收款方的余額 19 double recMoney=rs.getDouble("money"); 20 21 //設置手動管理事務 22 conn.setAutoCommit(false); 23 24 //2.驗證付款方余額夠不夠 25 sql="select * from accounts_jiawen " 26 + "where id=?"; 27 ps=conn.prepareStatement(sql); 28 ps.setString(1, payId); 29 rs=ps.executeQuery(); 30 rs.next(); 31 double payMoney=rs.getDouble("money"); 32 if(payMoney<mny){ 33 System.out.println("余額不足"); 34 throw new SQLException("余額不足"); 35 } 36 37 38 39 //3.付款方余額減少n元 40 sql="update accounts_jiawen set " 41 + "money=? where id=?"; 42 ps=conn.prepareStatement(sql); 43 ps.setDouble(1, payMoney-mny); 44 ps.setString(2, payId); 45 ps.executeQuery(); 46 47 //假設此處有一個錯誤 48 // Integer.valueOf("abc"); 49 //4.收款方加n元 50 sql="update accounts_jiawen set " 51 + "money=? where id=?"; 52 ps=conn.prepareStatement(sql); 53 ps.setDouble(1, recMoney+mny); 54 ps.setString(2, recId); 55 ps.executeQuery(); 56 //當轉賬流程正常結束時統一提交事物 57 conn.commit(); 58 } catch (Exception e) { 59 //當轉賬過程發生異常時回滾事務 60 try { 61 conn.rollback(); 62 } catch (SQLException e1) { 63 // TODO Auto-generated catch block 64 e1.printStackTrace(); 65 throw new RuntimeException("回滾"); 66 } 67 e.printStackTrace(); 68 throw new RuntimeException("wrong",e); 69 }finally{ 70 DBUtil.close(conn); 71 } 72 }
首先,我們模擬兩個銀行的賬號,一個給另一個匯款,首先我們要先查詢一下收款人的信息,這和符合世紀邏輯,因為我們要知道收款人是否存在,同樣是做一個select查詢語句,方法依然使用的是前述的ParperdStatement,設置占位符的方法,判斷收款人是否存在,並記錄下收款方的現在余額,以便在收到錢後做一個累加。
然後重點來了,在第22行,這就是手動管理一個事物,將自動管理事物關閉,這樣以上的代碼就不是意見完整的事,否則他就是完整的一件事,而不會等待下面的過程,但實際上收款與匯款整體才能算上一件事,因為這涉及到兩個人的賬戶金額變動問題,你不能只考慮其中一個人的金額變化,而將它設置為false以後上面的過程就會一起等待下面的過程。第二部是驗證付款方的余額,判斷是否夠轉。後面的過程就是收付雙方金額的變動,如果你不把事物設置為手動的話,在第四步上面模擬一個代碼打斷一下下面代碼的執行,那麼後面的金額將發生錯誤。這裡面還要說明的是conn.commit()是統一提交事務,而conn.rollback()為回滾
見名知意,就是集中到一起發送一組SQL,好處也就是效率更高,降低了數據庫和程序之間的網絡調用,直接給出示例代碼:
@Test public void test3(){ Connection conn=null; try { conn=DBUtil.getConnection(); conn.setAutoCommit(false); //批量發送數據的前提是他們的SQL一樣 String sql="insert into emp_jiawenzhe values(" + " emp_jiawenzhe_w.nextval,?,?,?,?,?,?,?)"; PreparedStatement ps =conn.prepareStatement(sql); for(int i=1;i<=108;i++){ ps.setString(1, "好漢"+i); ps.setString(2, "打劫"); ps.setInt(3, 0); ps.setDate(4, null); ps.setDouble(5, 1000.0); ps.setDouble(6, 8000.0); ps.setInt(7, 3); //將本條數據存到ps內 ps.addBatch(); //每30次批量發送一次數據 if(i%30==0){ ps.executeBatch(); //清除緩存的數據 ps.clearBatch(); } } //余下的數據單獨發送一次 ps.executeBatch(); conn.commit(); } catch (SQLException e) { try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); throw new RuntimeException("批量添加員工失敗",e); }finally{ DBUtil.close(conn); } }
這是一個批量插入員工的示例,首先要說明的是,批量發送SQL,前提是他們的SQL要一致,就是干的都是同樣的一種事,在開始進行批處理之前,先要把事物的設置設置為手動提交,下一步就是建立SQL的變參數模型,比如說這裡面我建立的是一個插入的模型,下面的for循環完全是為了省事,做出了插入108條數據的寫法,當然你也可以逐一寫
下面我將說明一下addBatch()這個方法,我們在寫了108條數據並不是直接就寫到了數據庫中,他同樣是要調用ParperdStatement的發送方法,但這就不是excuteQuery,寫出來的東西要先放到緩存中,然後從緩存中拿到一部分數據發送數據庫,再將發送完了的SQL清除緩存,下面做的是每30條發送一次的代碼,調用excuteBatch發送,然後調用clearBatch清除緩存,當然最後剩下的要再一次調用excuteBatch一次性發送走,最後不要忘記提交,因為之前已經設置成了手動提交。
首先對於這塊我認為不太好理解,用起來似乎也沒有上面那麼簡單,接下來同樣是引用一個示例加以說明:
這個示例是這個樣子的,首先要有兩張表,一張是部門表,一張是員工表,這兩個表是關聯的,即主表部門表的主鍵deptno在從表的列中也有出現,並成為從表的外鍵,當對主表進行插入的時候,能將從表的信息一並插入
1 @Test 2 public void test4(){ 3 //假設要添加的部門數據如下 4 String dname ="財務部"; 5 String loc="北京"; 6 //假設要添加員工的數據如下 7 String ename="張三"; 8 String job="經理"; 9 int mgr=0; 10 double sal=8000.0; 11 double comm=2000.0; 12 13 String ename2="李四"; 14 String job2="經理"; 15 int mgr2=0; 16 double sal2=5000.0; 17 double comm2=500.0; 18 19 Connection conn=null; 20 try { 21 conn=DBUtil.getConnection(); 22 conn.setAutoCommit(false); 23 //先添加部門 24 String sql ="insert into depts_jiawen " 25 + " values(depts_seq_jiawen.nextval,?,?) " ; 26 //參數二是一個數組存的是希望被ps記住的字段名字 27 PreparedStatement ps=conn.prepareStatement(sql,new String[]{"deptno"}); 28 ps.setString(1, dname); 29 ps.setString(2, loc); 30 ps.executeUpdate(); 31 32 //從ps中獲取它之前記錄的字段值 33 //返回的結果集中只有一種數據 34 //存的就是記錄那些字段的值 35 ResultSet rs=ps.getGeneratedKeys(); 36 rs.next(); 37 int deptno=rs.getInt(1); 38 39 //再添加員工 40 sql="insert into emp_jiawenzhe values( " 41 + "depts_seq_jiawen.nextval,?,?,?,?,?,?,?) "; 42 ps=conn.prepareStatement(sql); 43 ps.setString(1, ename); 44 ps.setString(2, job); 45 ps.setInt(3, mgr); 46 ps.setDate(4, null); 47 ps.setDouble(5, sal); 48 ps.setDouble(6, comm); 49 ps.setInt(7, deptno); 50 ps.executeUpdate(); 51 52 ps=conn.prepareStatement(sql); 53 ps.setString(1, ename2); 54 ps.setString(2, job2); 55 ps.setInt(3, mgr2); 56 ps.setDate(4, null); 57 ps.setDouble(5, sal2); 58 ps.setDouble(6, comm2); 59 ps.setInt(7, deptno); 60 ps.executeUpdate(); 61 62 conn.commit(); 63 64 } catch (SQLException e) { 65 try { 66 conn.rollback(); 67 } catch (SQLException e1) { 68 // TODO Auto-generated catch block 69 e1.printStackTrace(); 70 } 71 e.printStackTrace(); 72 throw new RuntimeException("wrong",e); 73 }finally{ 74 DBUtil.close(conn); 75 } 76 }
這裡面可以看到ParperdStatement中傳入了兩個參數,第一個和之前一樣,是要執行的SQL,而第二個參數是一個字符串數組,他是希望被記住的字段名字,我的理解就是那個外鍵字段放到裡面,為了後面取到這個外鍵的值傳入到從表中進行更新,然後調用了getGeneratedKeys()獲得了這些主鍵的結果集,再用int deptno=rs.getInt(1);得到對應字段的值,對於這塊我實在不能明朗的解釋清楚,目前我也只能說先這麼記住吧,而後面的插入從表就是一個批處理操作,與前述相同。
分頁就是對於一個更龐大的數據表當我們不希望看到整表時,可以分成幾段呈現,這個內容就是一個固定的模式,有固定的分頁公式,一目了然,用的時候直接拿過來使用就好了
示例代碼:
1 @Test 2 public void test5(){ 3 int size=10; 4 int page=2; 5 6 Connection conn=null; 7 try { 8 conn=DBUtil.getConnection(); 9 String sql="select * from( " 10 + "select e.*,rownum r from ( " 11 + "select * from emp_jiawenzhe " 12 + "order by empno " 13 + ") e " 14 + ") where r between ? and ?"; 15 PreparedStatement ps=conn.prepareStatement(sql); 16 ps.setInt(1, (page-1)*size+1); 17 ps.setInt(2, size*page); 18 ResultSet rs=ps.executeQuery(); 19 while (rs.next()) { 20 System.out.println(rs.getInt("empno")+","+rs.getString("ename")); 21 22 } 23 } catch (SQLException e) { 24 25 e.printStackTrace(); 26 throw new RuntimeException("wrong",e); 27 }finally{ 28 DBUtil.close(conn); 29 } 30 }
page和size分別是分幾頁,每頁有幾個,分頁的SQL寫法是數據庫內容,我將在數據庫基本SQL使用中提到,這裡面暫時先這麼寫著,後面的內容也就很清楚了!
未完待續!