事務
要么都成功,要么都失敗。
在一個事件中的事:從通知開啟事務,到提交事務之間,任何一句語句有錯誤,則哪一句語句都不應該提交。
1、事務的幾個狀態
1、開啟事務
2、事務提交 commit()。在做事務提交和回滾前,關鍵的頭尾是:(1)通知開啟事務,false是開啟(connection.setAutoCommit(false);) (2)提交或回滾(提交寫在try部分,回滾寫在catch中)
3、事務回滾 rollback()。事務一旦提交(commit)了,就無法回滾(rollback)了。所以,應該在提交之前判斷,如果崩了就不要提交,直接回滾。
4、關閉事務
轉賬:
A:1千塊
B:1千塊
A --> B: A(900)、B(1100)
不能出現A轉給了B100塊錢,A成為900,但是系統崩潰了,B還沒收到100塊錢,出現了A900塊錢,B1000塊錢的情況。
1、搭建環境,進行測試

1 Create TABLE account( 2 id INT PRIMARY KEY auto_increment, 3 `name` VARCHAR(40), 4 money FLOAT 5 ); 6 INSERT INTO account(`name`,money) VALUES ('A',1000); 7 INSERT INTO account(`name`,money) VALUES ('B',1000); 8 INSERT INTO account(`name`,money) VALUES ('C',1000);
2、項目構成
1、建的普通Maven項目(不用模板)
2、在java下建con.wang.utils文件夾,並在該文件夾下建兩個class文件。
3、兩個class文件中,一個是JDBC的工具類(JdbcUtils),公共的資源配置放在這;另一個是具體的操作數據庫的類(Test_jdbc)
4、連接數據庫的用戶名,密碼,數據庫驅動和url都放在resources下的db.properties文件夾中。

driver = com.mysql.jdbc.Driver url = jdbc:mysql://localhost:3306/jdbc_example?useUnicode=true&characterEncoding=utf8&useSSL=true username = root password = 123456
5、導入mysql依賴。此外,為實現上一節提到的junit單元測試,還要導入junit依賴

<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
3、寫JDBC的工具類(JdbcUtils)

package com.wang.utils; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class JdbcUtils { private static String driver = null; private static String url = null; private static String username = null; private static String password = null; static { try { InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties"); Properties properties = new Properties(); properties.load(in); driver = properties.getProperty("driver"); url = properties.getProperty("url"); username = properties.getProperty("username"); password = properties.getProperty("password"); //驅動只用加載一次 Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } } //獲取連接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } //釋放連接資源 public static void release(Connection conn, Statement st, ResultSet rs){ if(rs!=null){ try { rs.close(); }catch (SQLException e){ e.printStackTrace(); } } if(st!=null){ try { st.close(); }catch (SQLException e){ e.printStackTrace(); } } if(conn!=null){ try { conn.close(); }catch (SQLException e){ e.printStackTrace(); } } } }
4、寫具體的操作數據庫的類(Test_jdbc)
—— 模擬轉賬,並測試失敗情況下的回滾和成功情況下的事務提交
情況1:當寫第4步,故意制造錯誤的時候,看看第3步的SQL語句,提交還是回滾

1 package com.wang.utils; 2 import org.junit.Test; 3 4 import java.sql.Connection; 5 import java.sql.DriverManager; 6 import java.sql.SQLException; 7 public class Test_jdbc { 8 @Test 9 public void test() throws Exception { 10 //配置信息 11 //String driver = "com.mysql.jdbc.Driver"; 12 String url = "jdbc:mysql://localhost:3306/jdbc_example?useUnicode=true&characterEncoding=utf8&useSSL=true"; 13 String username = "root"; 14 String password = "123456"; 15 Connection connection = null; 16 try { 17 //第一步,加載驅動 18 Class.forName("com.mysql.jdbc.Driver"); 19 //第二步,連接數據庫 20 connection = DriverManager.getConnection(url, username, password); 21 //第三步,通知開啟事務。false是開啟 22 connection.setAutoCommit(false); 23 //第四步,執行SQL語句 24 String sql = "update account set money = money - 100 where name = 'A'"; 25 connection.prepareStatement(sql).executeUpdate(); 26 //第五步,制造錯誤 27 int i = 1/0; 28 //第六步,寫第二句SQL語句。理論上,第五步出現錯誤,所以這一句應該也不執行,不應該提交,應該回滾 29 String sql2 = "update account set money = money + 100 where name = 'B'"; 30 connection.prepareStatement(sql2).executeUpdate(); 31 //第七步,提交 32 connection.commit(); 33 //第八步,若上面兩天SQl都提交成功,則輸出“提交成功” 34 System.out.println("提交成功!"); 35 } catch (Exception e) { 36 try { 37 //如果try中的方法有錯誤,就走catch,通知數據庫執行回滾事務 38 connection.rollback(); 39 } catch (SQLException e1) { 40 e1.printStackTrace(); 41 } 42 e.printStackTrace(); 43 } finally { 44 //釋放資源 45 connection.close(); 46 } 47 } 48 }
2、當注釋掉第五步,代碼沒有錯誤,則提交事務

package com.wang.utils; import org.junit.Test; import java.sql.*; public class Test_jdbc { @Test public void test() throws Exception { Connection connection = null; Statement st = null; ResultSet rs = null; try { //第一步,連接數據庫 connection = JdbcUtils.getConnection(); //第二步,通知開啟事務。false是開啟 connection.setAutoCommit(false); //第三步,執行SQL語句 String sql = "update account set money = money - 100 where name = 'A'"; connection.prepareStatement(sql).executeUpdate(); //第四步,故意制造錯誤 //int i = 1/0; //第五步,寫第二句SQL語句。 //注釋掉第四步錯誤,第三步和第五步的sql和sql2語句都提交 String sql2 = "update account set money = money + 100 where name = 'B'"; connection.prepareStatement(sql2).executeUpdate(); //第六步,提交 connection.commit(); //第七步,若上面兩條SQl都提交成功,則輸出“提交成功” System.out.println("提交成功!"); } catch (Exception e) { try { //如果try中的方法有錯誤,就走catch,通知數據庫執行回滾事務 connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } finally { //釋放資源 JdbcUtils.release(connection,st,rs); } } }
5、上述的關鍵在第3步,通知開啟事務:connection.setAutoCommit(false); false是開啟
若不通知開啟事務,即使有錯誤,也不會回滾,直接就提交了。