本篇將講訴如何使用JDBC進行數據庫有關事務的操作。在上一篇博客中已經介紹了事務的概念,和在MySQL命令行窗口進行開啟事務,提交事務以及回滾事務的操作。
似乎事務和批處理都可以一次同時執行多條SQL命令,但是事務是如果某一條SQL出錯,則前面已經執行過的SQL全部都將回滾;而批處理中某一條SQL出錯,那么這條出錯的SQL要么會拋出個異常,要么以一個代表出錯的值返回,已經執行過的SQL不受影響,至於后面的SQL是否還會執行則看數據庫,不同數據庫有不同的處理。
在前一篇我們說過,數據庫對於事務是默認自動提交的,也就是發一條SQL命令則數據庫就執行一條。而對於JDBC而言,當向數據庫獲取一個鏈接Connection對象,在默認情況下通過Connection對象發送的SQL命令也是默認自動提交事務的。
操作:
① 如果我們想使用JDBC對多條SQL進行整體執行,需要先提交事務命令,這一步是通過Connection對象先將自動提交關閉,調用Connection對象的setAutoCommit(false)方法即可。這個方法相當於在MySQL命令行窗口中輸入”start transaction”命令。
② 如果我們在JDBC已經將自動提交關閉的情況下需要提交事務,則調用Connection對象的commit()方法即可。
③ 如果我們在JDBC已經將自動提交關閉的情況下需要回滾事務,則調用Connection對象的rollback(…)方法即可。rollback方法如果是無參,則回滾前面所有已執行的SQL命令;如果是有參,則可以指定回滾點,保留前面部分指定的已執行的SQL命令。
下面以幾個案例來像上一篇博客一樣分別介紹幾種事務的相關操作。
例1:正確提交事務的小案例
在這個簡單的案例中,通過用戶A向用戶B轉賬100元來快速了解如何使用JDBC操作事務。
創建數據庫和表,另外再添加兩條數據:
create database jdbcdemo; use jdbcdemo; create table account( id int primary key auto_increment, name varchar(40), money double ); insert into account(name,money) values('a',1000); insert into account(name,money) values('b',1000);
先看看准備的數據:
創建工程,在工程中導入數據庫連接驅動的jar包。在【src】目錄下新建一個database.properties文件,內容如下:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcdemo
username=root
password=root
構建JDBC的工具類,包括注冊驅動,獲取連接,釋放資源和連接等,這部分同《JDBC操作數據庫的學習(2)》中相同,此處略。
使用JDBC完成事務操作的demo代碼如下:

1 public void transaction() throws SQLException { 2 Connection conn = null; 3 PreparedStatement st = null; 4 ResultSet rs = null; 5 try{ 6 conn = JdbcUtils.getConnection(); 7 conn.setAutoCommit(false); //開啟事務,相當於start transaction命令
8
9 String sql1 = "update account set money=money-100 where name='a'"; 10 st = conn.prepareStatement(sql1); 11 st.executeUpdate(); //執行SQL語句
12
13 String sql2 = "update account set money=money+100 where name='b'"; 14 st = conn.prepareStatement(sql2); 15 st.executeUpdate(); //執行SQL語句
16
17 conn.commit(); //提交事務
18 }finally{ 19 JdbcUtils.release(conn, st, rs); 20 } 21 }
查看事務執行情況:
通過例1中簡單的代碼就完成了事務的一系列操作。可以看到用戶A向用戶B確實轉賬了100元。
例2:事務執行過程出錯回滾的小案例
通過前一篇博客,我們知道如果在事務執行過程中發生了錯誤,則數據庫將會使該事務中所有的操作都回滾,那么我們在使用JDBC的情況下也來重新模擬一次,依然還是用戶A向用戶B轉賬100元。
將例1中的account表所有用戶的金額重新制定為1000元的SQL命令:
update account set money=1000;
創建工程,在工程中導入數據庫連接驅動的jar包。在【src】目錄下新建一個database.properties文件,內容如下:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcdemo
username=root
password=root
構建JDBC的工具類,包括注冊驅動,獲取連接,釋放資源和連接等,這部分同《JDBC操作數據庫的學習(2)》中相同,此處略。
在使用JDBC進行事務處理中,我們添加一個顯而易見的錯誤:int x = 1/0 ,如下代碼:

1 public void transaction() throws SQLException { 2 Connection conn = null; 3 PreparedStatement st = null; 4 ResultSet rs = null; 5 try{ 6 conn = JdbcUtils.getConnection(); 7 conn.setAutoCommit(false); //開啟事務,相當於start transaction命令
8
9 String sql1 = "update account set money=money-100 where name='a'"; 10 st = conn.prepareStatement(sql1); 11 st.executeUpdate(); 12
13 int x = 1/0; //在此處模擬事務處理過程中出錯
14
15 String sql2 = "update account set money=money+100 where name='b'"; 16 st = conn.prepareStatement(sql2); 17 st.executeUpdate(); 18
19 conn.commit(); //提交事務
20 }finally{ 21 JdbcUtils.release(conn, st, rs); 22 } 23 }
當我們執行這個Java方法時,由於設置了int x = 1/0這個邏輯錯誤,程序會拋出異常,但是因為拋出異常后,后面的代碼不再執行,也就是說程序無法執行到提交事務conn.commit()方法處,因此數據庫將會回滾該事務所有的操作,因此A與B的金額還是原來那樣:
例3:事務執行過程出錯由開發者手動回滾的小案例
在例2中當事務執行過程中出錯時,會由數據庫自動回滾所有的操作,而在JDBC中,我們也可以調用鏈接Connection對象的rollback()方法回滾所有的操作,在下面的案例中,我們將在捕獲異常的代碼塊中手動回滾所有的操作。
所有表、表中數據、配置文件和JDBC工具類都同例2相同。
將例2中的代碼修改為如下:

1 public void transaction() throws SQLException { 2 Connection conn = null; 3 PreparedStatement st = null; 4 ResultSet rs = null; 5 try{ 6 conn = JdbcUtils.getConnection(); 7 conn.setAutoCommit(false); //開啟事務,相當於start transaction命令
8
9 String sql1 = "update account set money=money-100 where name='a'"; 10 st = conn.prepareStatement(sql1); 11 st.executeUpdate(); 12
13 int x = 1/0; //在此處模擬事務處理過程中出錯
14
15 String sql2 = "update account set money=money+100 where name='b'"; 16 st = conn.prepareStatement(sql2); 17 st.executeUpdate(); 18
19 conn.commit(); //提交事務
20 }catch (Exception e) { 21 e.printStackTrace(); 22 conn.rollback(); //手動回滾該事務中所有的操作
23 }finally{ 24 JdbcUtils.release(conn, st, rs); 25 } 26 }
效果和例2也是一樣的,事務出錯則回滾所有的操作。
例4:指定事務回滾點的案例
例3的手動回滾其實有些雞肋,與其說是手動回滾,其實即使沒有調用rollback該事務就不會執行成功,而rollback方法更高級的功能在於能回滾到指定的地方。
通過鏈接Connection對象的setSavepoint()方法即可在該方法所在的位置設置回滾點對象,當調用rollback(回滾點對象)方法即可將事務回滾到這個位置。這樣在執行回滾之后,再次調用提交事務(Commit),則回滾點之前的SQL還是執行的。
以例2為前提,我們在會發生異常地方的前面設置回滾點,而使第一條SQL語句能被執行,即用戶A的操作能執行(減少100),用戶B的操作不能執行(金額不變)。
所有表、表中數據、配置文件和JDBC工具類都同例2相同。
將例2中的代碼修改為如下:

1 public void transaction() throws SQLException { 2 Connection conn = null; 3 PreparedStatement st = null; 4 ResultSet rs = null; 5 Savepoint sp = null; //代表回滾點對象
6 try{ 7 conn = JdbcUtils.getConnection(); 8 conn.setAutoCommit(false); //開啟事務,相當於start transaction命令
9
10 String sql1 = "update account set money=money-100 where name='a'"; 11 st = conn.prepareStatement(sql1); 12 st.executeUpdate(); 13
14 sp = conn.setSavepoint(); //在此處設置回滾點
15
16 int x = 1/0; //在此處模擬事務處理過程中出錯
17
18 String sql2 = "update account set money=money+100 where name='b'"; 19 st = conn.prepareStatement(sql2); 20 st.executeUpdate(); 21
22 conn.commit(); //提交事務
23 }catch (Exception e) { 24 conn.rollback(sp); //回滾到指定的回滾點處
25 conn.commit(); //回滾之后再次提交事務保證回滾點之前的SQL能被執行
26
27 }finally{ 28 JdbcUtils.release(conn, st, rs); 29 } 30 }
查看事務執行情況:
可以看到結果正如我們希望的那樣,由於用戶A的操作在回滾點之前,又因為執行回滾之后還執行了提交事務,因此回滾點之前的SQL命令還是可以被執行成功的。因此切記,要想使用回滾點,一定要在回滾之后再次提交事務,否則設置回滾點是沒有意義的。
下圖是一張上面例子的執行流程: