3.1 異常的使用說明

  1. 在工具類中(JDBCUtils)的方法最好宣告異常(throws),以便後續實現類中去捕獲這些異常。

    • 工具類中捕獲異常通常沒有意義 eg:實現類中connection建立過程出現異常,若工具類中使用try-catch則實現類處理異常後後續仍可繼續執行,此時後續執行便無意義。
  2. 在實現類中去捕獲並處理異常,且可使用fially去將開啟的資源(connection, preparedStatement, I/O)關閉

3.2 事務簡介

3.2.1 事務&事務處理

  • 事務:一組邏輯操作單元,使資料從一種狀態轉變為另一種狀態
  • 事務處理(事務操作):當在一個事務中執行多個操作時,要麼所有的事務都被提交(commit),那麼這些修改就永久地儲存下來;要麼資料庫管理系統將放棄所作的所有修改,整個事務回滾(rollback)到最初狀態。

3.2.2 JDBC事務處理

  • 事務一旦提交就不可回滾

    • 當一個連線物件被建立時,預設情況下是自動提交事務AutoCommit(true):每次執行一個 SQL 語句時,如果執行成功,就會向資料庫自動提交,而不能回滾。
    • 關閉資料庫連線,資料就會自動的提交。 如果多個操作,每個操作使用的是自己單獨的連線,則無法保證事務。即同一個事務的多個操作必須在同一個連線下。

JDBC的事務處理:

  1. 呼叫Connection物件的setAutoCommit(false)取消自動提交事務
  2. 所有SQL語句執行成功後呼叫commit()提交事務
  3. 出現異常時在catch裡呼叫rollback()回滾事務
    • 若此時 Connection 沒有被關閉,還可能被重複使用,則需要恢復其自動提交狀態 setAutoCommit(true)。尤其是在使用資料庫連線池技術時,執行close()方法前,建議恢復自動提交狀態。

點選檢視例項
    @Test//updateRollBack()實現sql語句執行
public void testUpdate(){
Connection connection = null; try {
//1.取消資料的自動提交
connection = JDBCUtils.getConnection();
connection.setAutoCommit(false); String sql1 = "UPDATE account\n" +
"SET balance = balance - 100\n" +
"WHERE id = ?;";
String sql2 = "UPDATE account\n" +
"SET balance = balance + 100\n" +
"WHERE id = ?;"; updateRollBack(connection, sql1, 1); //System.out.println(10 / 0); updateRollBack(connection, sql2, 2); //2.提交資料
connection.commit(); System.out.println("轉賬成功!");
} catch (Exception e){
e.printStackTrace();
System.out.println("轉賬失敗!"); try {
//3.出現異常資料回滾
connection.rollback();
} catch (Exception e2){
e2.printStackTrace();
}
} finally {
JDBCUtils.closeResource(connection, null);
}
}

3.2.3 事務的四大屬性(ACID)

  1. 原子性Atomicity: 過程不可分割

    • 指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
  2. 一致性Consistency: 操作前與操作後總量不變
    • 使資料庫從一個一致性狀態變換到另外一個一致性狀態。
  3. 隔離性Isolation: 多事務操作時,互相不會產生影響
    • 指一個事務的執行不能被其他事務干擾,即一個事務內部的操作及使用的資料對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
  4. 永續性Durability: 事務提交後表中資料發生變化
    • 一個事務一旦被提交,它對資料庫中資料的改變就是永久性的,接下來的其他操作和資料庫故障不應該對其有任何影響。

3.2.4 資料庫的併發問題&隔離級別

1-併發問題

  1. 髒讀:讀到了已更新而未提交的資料

    • 對於兩個事務 T1, T2, T1 讀取了已經被 T2 更新但還沒有被提交的欄位。之後, 若 T2 回滾, T1讀取的內容就是臨時且無效的。
  2. 不可重複讀: 讀到了已更新(update)已提交欄位
    • 對於兩個事務T1, T2, T1 讀取了一個欄位, 然後 T2 更新了該欄位。之後, T1再次讀取同一個欄位, 值就不同了。
  3. 幻讀: 讀到了已插入(insert)已提交欄位
    • 對於兩個事務T1, T2, T1 從一個表中讀取了一個欄位, 然後 T2 在該表中插入了一些新的行。之後, 如果 T1 再次讀取同一個表, 就會多出幾行。

2-隔離級別

  • 一個事務與其他事務隔離的程度稱為隔離級別,隔離級別越高,資料一致性越好,但併發性越差。
  1. READ UNCOMMITTED(讀未提交):允許事務讀取其他食物未被提交的變更。允許髒讀的出現
  2. READ COMMITTED(讀已提交):只允許事務讀取已被其他事物提交的變更。不允許髒讀,允許可重複讀
  3. REPEATABLE READ(可重複讀):確保事務可以多此從一個欄位中讀取相同的值,事務持續期間禁止任何其他事務對這個欄位進行更新。 避免髒讀和可重複讀
  4. SERIALIZABLE(序列化):確保事務可以從一個表中讀取相同的行,事務持續期間禁止其他事務對該表進行CUD

3-MySQL中設定隔離級別

  1. 檢視當前隔離級別:SELECT @@tx_isolation;
  2. 設定當前MySQL連線隔離級別:set transaction isolation level read committed;
  3. 設定資料庫系統的全域性隔離級別 : set global transaction isolation level read committed;
  4. 補充
    1. 建立mysql資料庫使用者:create user tom identified by 'abc123';
    2. 授予許可權:
      1. 授予通過網路方式登入的tom使用者,對所有庫所有表的全部許可權,密碼設為root

        grant all privileges on *.* to tom@'%' identified by 'root';
      2. 給tom使用者使用本地命令列方式,授予user_db這個庫下的所有表的插刪改查的許可權。

        grant select,insert,delete,update on user_db.* to tom@localhost identified by 'abc123';

4-Java中設定隔離級別

  1. 設定隔離級別:connection.setTranscationIsolation(Connection.XXX);
  2. 獲取隔離級別:connection.getTransactionIsolation();

點選檢視隔離級別測試程式碼
    @Test
public void select() throws Exception{
Connection connection = JDBCUtils.getConnection(); //獲取當前隔離級別
System.out.println(connection.getTransactionIsolation()); //設定隔離級別
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); connection.setAutoCommit(false); String sql = "select * from account;";
List<Account> list = AllSelect.getInstance(connection, Account.class, sql);
for(int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
}
} @Test
public void update() throws Exception{
Connection connection = JDBCUtils.getConnection();
String sql = "update account set balance = balance + 100 where id = ?"; connection.setAutoCommit(false); PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1, 3); preparedStatement.executeUpdate(); //執行緒休眠15s
Thread.sleep(15000); //回滾測試髒讀
connection.rollback(); }