10.2(java學習筆記)JDBC事務簡述
一、事務
事務是指作為一系列操作組成的一個整體,該整體只有兩種狀態,要麼全部執行,要麼全部不執行。
當組成這個事務的所有語句都執行成功則該事務執行,只要有一條語句執行失敗則該事務不執行。
假設這裡有一個insert語句和一個update語句屬於一個事務,從巨集觀上來看,這個事務的狀態只有執行或者不執行。
從微觀上看,這個事務是由這兩條語句組成的, 每個語句必定有其個體的狀態(成功或者不成功)。
比如可能是insert成功,update失敗。也可能是insert失敗,update成功。
當其中只要有一條語句失敗,則傳送回滾(回到該事務執行前的狀態即所有事務都不執行,已成功執行的語句會作廢)。
該事務不執行。如果所有語句都成功,則該事務執行。
那麼事務是如何劃分的呢,怎麼樣算事務的開始,怎麼樣又算事務的結束呢?
2.1事務開始
-連線到資料庫上,並執行了一條DML語句(insert、update、delete)。
-前一個事務結束後,新輸入的DML語句。
2.2事務結束
-執行commit,或rollback語句。
-執行一條DDL語句,如create table,這種情況下回自動執行commit語句。
-執行一條DCL語句,如grant語句,在這種情況下回自動執行commit語句。
-斷開與資料庫連線
-執行一條DML語句出現錯誤時,在這種情況下為這個無效的語句執行rollback。
DDL、DCL、DML可參閱:https://www.cnblogs.com/kawashibara/p/8961646.html
假設這裡有五條語句:
-select
-insert ---事務開始---
-updata
-delete
-insert ---
commit or DCL or DDL ---
結合上面分析,事務從insert開始,如果所有語句都能正常執行,
那麼事務從insert開始,到commit or DCL or DDL結束,這代表一個事務。
假如其中只要有一條DML語句執行錯誤,比如第二個insert執行錯誤,
那麼事務從第一個insert開始,第二個到insert結束。這個事務呈現出一種不執行的狀態。
即insert updata delete insert都不執行,可以看做回到了第一個insert之前的狀態(回滾rollback)。
這裡需要用到一個函式:
void rollback();撤消當前事務中所做的所有更改,並釋放此連線物件當前支援的任何資料庫鎖。
僅當禁用自動提交模式時才應使用此方法。
但DML語句出現異常時,我們需要呼叫此方法達到回滾(回到該事務執行前的狀態,即該事務沒有執行)。
我們先來看這樣一個例子:
insert(正常執行) --> insert(正常執行) -->commit();
事務從insert開始,到commit結束。這整個事務是執行的。(測試前最好把表清空下,避免插入重複的主鍵導致錯誤)
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class TestJDBC{ public static void main(String[] args){ final String connectionUrl = "jdbc:mysql://localhost:3306/mybatis"; String userName = "root"; String passWord = "123456"; String insertT = "INSERT INTO `mybatis`.`tadd`" //正確的insert語句 + "(`id`, `tname`, `tpwd`, `tstudentnum`) " + "VALUES (?, ?, ?, ?);"; String insertF = "INSERT INTO `mybatis`.`tadd`" //錯誤的insert語句 + "(`id`, `tname`, `tpwd`, `tstudentnum`) " + "VALUES (?, ?, ?, ?, ?, ?);"; Connection conn = null; PreparedStatement p1 = null; PreparedStatement p2 = null; try { //載入驅動 Class.forName("com.mysql.cj.jdbc.Driver"); //建立連線 conn = DriverManager.getConnection(connectionUrl,userName,passWord); conn.setAutoCommit(false);//將自動提交設為false,即不進行自動提交 p1 = conn.prepareStatement(insertT);//第一個insert語句,事務開始(正確的語句) setParam(p1,"1","1","1","1"); p1.execute(); System.out.println("p1插入完成"); Thread.sleep(5000);//休眠5s p2 = conn.prepareStatement(insertT);//第二個insert語句 (正確的語句) setParam(p2,"2","2","2","2"); p2.execute(); System.out.println("p2插入完成"); conn.commit();//手動提交 System.out.println("執行成功"); } catch (SQLException e) { try { conn.rollback();//事務回滾 } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ //依次關閉連線 try{ p1.close(); }catch(Exception e){ } try{ p2.close(); }catch(Exception e){ } try{ conn.close(); }catch(Exception e){ } } } public static void setParam(PreparedStatement p,Object... o){ int i = 1; for(Object temp:o){ try { p.setObject(i, temp); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } i++; } } }
執行結果:
p1插入完成
p2插入完成
執行成功
p1插入成功後會等待5s,5s之後p2進行插入,到最後commit整個事務的狀態是執行的。
我們可以看下資料庫中的資料是插入成功的。
接下來我們去看這樣一個例子:
insert(正常執行) --> insert(執行錯誤) -->commit();
事務從insert開始,到錯誤的insert結束,整個事務是不執行的。
即第一個insert不執行,第二個insert也不執行。
(由於測試資料依然採用之前的資料,所以測試前需先將表清空)
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class TestJDBC{ public static void main(String[] args){ final String connectionUrl = "jdbc:mysql://localhost:3306/mybatis"; String userName = "root"; String passWord = "123456"; String insertT = "INSERT INTO `mybatis`.`tadd`" + "(`id`, `tname`, `tpwd`, `tstudentnum`) " + "VALUES (?, ?, ?, ?);"; String insertF = "INSERT INTO `mybatis`.`tadd`" + "(`id`, `tname`, `tpwd`, `tstudentnum`) " + "VALUES (?, ?, ?, ?, ?, ?);"; Connection conn = null; PreparedStatement p1 = null; PreparedStatement p2 = null; try { //載入驅動 Class.forName("com.mysql.cj.jdbc.Driver"); //建立連線 conn = DriverManager.getConnection(connectionUrl,userName,passWord); conn.setAutoCommit(false);//將自動提交設為false,即不進行自動提交 p1 = conn.prepareStatement(insertT);//正確的insert語句 setParam(p1,"1","1","1","1"); p1.execute(); System.out.println("p1插入完成"); Thread.sleep(5000); p2 = conn.prepareStatement(insertF);//錯誤insert的語句 setParam(p2,"2","2","2","2"); p2.execute(); System.out.println("p2插入完成"); conn.commit();//手動提交 System.out.println("執行成功"); } catch (SQLException e) { try { conn.rollback();//事務回滾 } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ //依次關閉連線 try{ p1.close(); }catch(Exception e){ } try{ p2.close(); }catch(Exception e){ } try{ conn.close(); }catch(Exception e){ } } } public static void setParam(PreparedStatement p,Object... o){ int i = 1; for(Object temp:o){ try { p.setObject(i, temp); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } i++; } } }
執行結果; p1插入完成 java.sql.SQLException: No value specified for parameter 5 at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:127) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:95) at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:414) at com.test.jdbc.TestJDBC.main(TestJDBC.java:41)
我們看下資料庫中的資料:
我們會發現,即使第一條插入語句執行了,但由於這個事務中第二條insert發生了錯誤,所以這個事務是不執行的。