為什麼需要交易

很多時候,在使用數據庫時,會出現需要執行許多不同操作的情況,但它們只有放在一起才有意義。

例如,我們正在編寫的銀行軟件應該做三件事:

  • 從客戶賬戶中提取資金
  • 將錢添加到收款人的帳戶
  • 將投寄資料記錄於「投寄紀錄」

如果在執行這些操作中的任何一個期間發生錯誤,則其他兩個操作也必須取消。不可能從客戶那裡註銷錢而不把它加到收款人身上?好吧,還是添加到收件人,但不從客戶端註銷?

因此,這種將不同操作合而為一的邏輯組合稱為事務。換句話說,事務是一組必須一起執行的操作。如果任何操作失敗或執行出錯,則必須取消所有其他操作。

一個事務通常有三種狀態:

  • 初始狀態——執行一組動作之前的系統狀態
  • 成功狀態——動作組完成後的狀態
  • 失敗狀態 - 出了點問題

在這種情況下,通常有三個命令:

  • begin/start - 在邏輯動作組開始之前執行
  • commit - 在事務操作組之後執行
  • 回滾- 啟動將系統從失敗狀態返回到初始狀態的過程

它是這樣工作的。

首先您需要打開一個事務——調用begin()start()方法。調用此方法表示出現問題時我們將嘗試返回的系統狀態。

然後執行所有操作,這些操作組合成一個邏輯組 - 事務。

然後調用commit()方法。它的調用標誌著一組邏輯動作的結束,通常也開始將這些動作付諸實踐的過程。

回想一下我們在 FileWriter 中是如何寫的:首先,我們寫的所有內容都存儲在內存中,然後當調用flush()方法時,將內存中緩衝區中的所有數據寫入磁盤。這個flush()是事務提交。

那麼,如果在事務運行過程中發生了錯誤,那麼就需要發起返回起始狀態的過程。這個過程稱為rollback(),同名方法通常負責它。

粗略地說,有兩種方式可以完成一筆交易:

  • COMMIT - 我們確認所做的所有更改
  • ROLLBACK - 回滾所有所做的更改

JDBC 中的事務

幾乎每個 DBMS 都可以處理事務。所以JDBC也有這種情況的支持。一切都非常簡單地實現。

首先,對Statement 對象的execute()方法的每次調用都在單獨的事務中執行。為此,Connection 有一個AutoCommit參數。如果設置為true,則每次調用execute()方法後都會調用commit()

其次,如果你想在一個事務中執行多個命令,那麼你可以這樣做:

  • 禁用自動提交
  • 調用我們的命令
  • 顯式調用commit()方法

它看起來很簡單:

connection.setAutoCommit(false);

Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount3 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");

connection.commit();

如果commit()方法運行時服務器發生錯誤,則 SQL 服務器將取消所有這三個操作。

但是在某些情況下,錯誤仍然發生在客戶端,而我們從未進行過commit()方法調用:

connection.setAutoCommit(false);

Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount3 = statement.executeUpdate("UPDATE multiple typos will result in an exception");

connection.commit();

如果在執行一次executeUpdate()時發生錯誤,則不會調用commit()方法。要回滾所有採取的操作,您需要調用rollback()方法。它通常看起來像這樣:

try{
  	connection.setAutoCommit(false);

  	Statement statement = connection.createStatement();
  	int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount3 = statement.executeUpdate("UPDATE multiple typos will result in an exception");

	  connection.commit();
 }
 catch (Exception e) {
   connection.rollback();
}

保存點

隨著 JDBC 3.0 的出現,可以更有效地處理事務回滾。現在可以設置save points-save points,調用rollback()操作時,回滾到特定的save point。

為了保存,您需要創建一個保存點,這是通過命令完成的:

Savepoint save = connection.setSavepoint();

恢復到保存點是通過以下命令完成的:

connection.rollback(save);

讓我們嘗試在有問題的命令之前添加一個保存點:

try{
  	connection.setAutoCommit(false);

  	Statement statement = connection.createStatement();
  	int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");

  	Savepoint save = connection.setSavepoint();
 	 try{
      	int rowsCount3 = statement.executeUpdate("UPDATE multiple typos will result in an exception");
 	 }
 	 catch (Exception e) {
    	   connection.rollback(save);
 	 }

	  connection.commit();
 }
 catch (Exception e) {
   connection.rollback();
}

我們通過在調用有問題的方法之前添加一個保存點來組織嵌套事務,並通過調用rollback(save)方法返回到保存的狀態。

是的,它與遊戲中的保存/加載非常相似。