3.1 異常的使用說明
- 在工具類中(JDBCUtils)的方法最好宣告異常(throws),以便後續實現類中去捕獲這些異常。
- 工具類中捕獲異常通常沒有意義 eg:實現類中connection建立過程出現異常,若工具類中使用try-catch則實現類處理異常後後續仍可繼續執行,此時後續執行便無意義。
- 在實現類中去捕獲並處理異常,且可使用fially去將開啟的資源(connection, preparedStatement, I/O)關閉
3.2 事務簡介
3.2.1 事務&事務處理
- 事務:一組邏輯操作單元,使資料從一種狀態轉變為另一種狀態
- 事務處理(事務操作):當在一個事務中執行多個操作時,要麼所有的事務都被提交(commit),那麼這些修改就永久地儲存下來;要麼資料庫管理系統將放棄所作的所有修改,整個事務回滾(rollback)到最初狀態。
3.2.2 JDBC事務處理
- 事務一旦提交就不可回滾
- 當一個連線物件被建立時,預設情況下是自動提交事務
AutoCommit(true)
:每次執行一個 SQL 語句時,如果執行成功,就會向資料庫自動提交,而不能回滾。 - 關閉資料庫連線,資料就會自動的提交。 如果多個操作,每個操作使用的是自己單獨的連線,則無法保證事務。即同一個事務的多個操作必須在同一個連線下。
- 當一個連線物件被建立時,預設情況下是自動提交事務
JDBC的事務處理:
- 呼叫Connection物件的setAutoCommit(false)取消自動提交事務
- 所有SQL語句執行成功後呼叫commit()提交事務
- 出現異常時在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)
- 原子性Atomicity: 過程不可分割
- 指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
- 一致性Consistency: 操作前與操作後總量不變
- 使資料庫從一個一致性狀態變換到另外一個一致性狀態。
- 隔離性Isolation: 多事務操作時,互相不會產生影響
- 指一個事務的執行不能被其他事務干擾,即一個事務內部的操作及使用的資料對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
- 永續性Durability: 事務提交後表中資料發生變化
- 一個事務一旦被提交,它對資料庫中資料的改變就是永久性的,接下來的其他操作和資料庫故障不應該對其有任何影響。
3.2.4 資料庫的併發問題&隔離級別
1-併發問題
- 髒讀:讀到了已更新而未提交的資料
- 對於兩個事務 T1, T2, T1 讀取了已經被 T2 更新但還沒有被提交的欄位。之後, 若 T2 回滾, T1讀取的內容就是臨時且無效的。
- 不可重複讀: 讀到了已更新(update)已提交欄位
- 對於兩個事務T1, T2, T1 讀取了一個欄位, 然後 T2 更新了該欄位。之後, T1再次讀取同一個欄位, 值就不同了。
- 幻讀: 讀到了已插入(insert)已提交欄位
- 對於兩個事務T1, T2, T1 從一個表中讀取了一個欄位, 然後 T2 在該表中插入了一些新的行。之後, 如果 T1 再次讀取同一個表, 就會多出幾行。
2-隔離級別
- 一個事務與其他事務隔離的程度稱為隔離級別,隔離級別越高,資料一致性越好,但併發性越差。
- READ UNCOMMITTED(讀未提交):允許事務讀取其他食物未被提交的變更。允許髒讀的出現
- READ COMMITTED(讀已提交):只允許事務讀取已被其他事物提交的變更。不允許髒讀,允許可重複讀
- REPEATABLE READ(可重複讀):確保事務可以多此從一個欄位中讀取相同的值,事務持續期間禁止任何其他事務對這個欄位進行更新。 避免髒讀和可重複讀
- SERIALIZABLE(序列化):確保事務可以從一個表中讀取相同的行,事務持續期間禁止其他事務對該表進行CUD
3-MySQL中設定隔離級別
- 檢視當前隔離級別:
SELECT @@tx_isolation;
- 設定當前MySQL連線隔離級別:
set transaction isolation level read committed;
- 設定資料庫系統的全域性隔離級別 :
set global transaction isolation level read committed;
- 補充
- 建立mysql資料庫使用者:
create user tom identified by 'abc123';
- 授予許可權:
- 授予通過網路方式登入的tom使用者,對所有庫所有表的全部許可權,密碼設為root
grant all privileges on *.* to tom@'%' identified by 'root';
- 給tom使用者使用本地命令列方式,授予user_db這個庫下的所有表的插刪改查的許可權。
grant select,insert,delete,update on user_db.* to tom@localhost identified by 'abc123';
- 授予通過網路方式登入的tom使用者,對所有庫所有表的全部許可權,密碼設為root
- 建立mysql資料庫使用者:
4-Java中設定隔離級別
- 設定隔離級別:
connection.setTranscationIsolation(Connection.XXX);
- 獲取隔離級別:
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();
}