Java面試準備十三:事務

這裡只是為了記錄,由於自身水平實在不怎麼樣,難免錯誤百出,有錯的地方還望大家多多指出,謝謝。

參考[瘋狂Java]JDBC:事務管理、中間點、批量更新

  1. 事務的理論級概念
  2. 關閉自動提交功能來開啟事務
  3. 中間點
  4. JDBC對事務的支援
  5. 事務配合批量更新

1. 事務的理論級概念

(1)事務具有ACID的特性:

Atomicity:原子性
Consistency:一致性
Isolation:隔離性
Durability:持續性

Atomicity:原子性
事務是最小的執行單位,不可分割,必須一次作為一個整體執行完;

Consistency:一致性
事務如果中間被割裂可能會導致資料的不一致性,因此事務最終的目的就是為了保證資料的完整性和一致性,而這個性質是由原子性來保障的;

Isolation:隔離性
併發事務之間不能相互影響(併發事務競爭的資料必然被同步監視),
原因很簡單,那就是原子性!併發事務之間不能看到對方的中間狀態!由此可見原子性是事務的根本屬性,其他特性都是由原子性保證的;

Durability:持續性
也稱為永續性,是指事務一旦提交,那麼對資料的修改就會永久儲存到物理儲存器中!未提交之前只是在記憶體映像中進行修改!

(2) 事務的內容和提交

  • 事務必須是由DML語句組成的:只是顯然的,事務就是為了防止修改資料時發生資料的不一致!但中間允許出現select語句,但是select語句並不屬於事務的一部分,因為select語句並不修改資料,僅僅就是臨時檢視一下結果而已;

  • Ⅱ 最多隻能出現一條DDL或者DCL語句,並且必須作為最後一句:因為DDL和DCL預設會自動觸發提交動作,出現DDL或者DCL就意味著事務已經結束。

  • Ⅲ 顯示提交和隱式提交:顯式提交就是手動顯示執行commit命令,隱式提交就是執行DML或DCL語句,在JDBC程式設計中順利從方法中正常退出也會隱式自動提交!

(3) 回滾

顯式回滾:手動顯示執行rollback命令;
隱式回滾:丟擲了沒有處理的異常;在JDBC程式設計中主動強行從一個方法中退出(強退 、exit等)也會觸發隱式回滾;

注 :
DML:
data manipulation language,它們是select、update、insert、delete,就像它的名字一樣,這4條命令是用來對資料庫裡的資料進行操作的語言。

DDL:
DDL比DML要多,主要的命令有CREATE 、ALTER 、DROP等,DDL主要是用在定義或改變表結構,資料型別,表之間的連線和約束等初始化工作上,他們大多在建立表時使用。

DCL:
是資料庫控制功能。是用來設定或更改資料庫使用者或角色許可權的語句,包括(grant,deny,revoke等)語句。在預設狀態下,只有sysadmin, dbcreator, db_ouner或db_securityadmin等人員才有權利執行DCL。

2. 關閉自動提交功能來開啟事務

(1)其實預設狀態下MySQL將每一條輸入的SQL命令都當做一個單獨的事務來處理,比如你輸入了一條insert into(DML)命令,它會立即執行並將修改直接更新奧物理儲存器上,這是因為MySQL預設將每一條SQL命令都當做一個單獨事務來了,並且執行一條命令就自動提交。

(2)MySQL預設將自動提交功能開啟了,即每輸入一條MySQL語句都會被當做一個單獨的事務並立即提交!因此,為了開啟事務功能,就必須將自動提交的功能關閉掉!

(3)開關命令:set autocommit=0|1; (這裡是SQL命令)
0表示關閉自動提交
1表示開啟自動提交
一旦開啟了事務功能,就可以順利執行DML語句了,一旦執行到DCL/DDL或者執行了commit就會提交由之前連續的DML組成的一個事務,而後面的語句將開啟一個新的事務。當然也可以用rollback命令來回滾事務;
示例如下:

drop table if exists emp;
create table emp(
emp_id int PRIMARY key,
emp_name VARCHAR(20)
)
set autocommit = 0;-- 設定手動提交事務
insert into emp values (1, 'jj');
insert into emp values(2, 't');
COMMIT; -- 提交事務
-- 執行完commit之後,上面的sql語句組成的事務結束
insert into emp values(3, 'bb'); -- 開啟一個新的事務
insert into emp values('c', 'cc');
COMMIT;

結果是隻有id為1、2的記錄能被插入到資料庫,id為3,’c’的記錄沒能被插入到資料庫,因為id為int,所以在插入id為’c’的記錄時出錯,而id為3,’c’的記錄是在一個事務裡面的,所有事務不能被成功執行。

注:在執行sql的過程中,如果出錯,則不會再執行下面的sql了。示例如下:

drop table if exists emp;
create table emp(
emp_id int PRIMARY key,
emp_name VARCHAR(20)
)
set  autocommit = 1;
insert  into emp values('c', 'bbb');  -- 如果sql語句出錯了的話,就不會再執行下面的語句了
insert into emp values(1, 'aaa');

(4)開啟臨時事務:

①當你在命令列對資料庫進行操作是可能不想set autocommit=0來關掉自動提交,而只是想臨時執行一段事務,這種需要是常見的;
②那麼可以輸入begin或者start transaction命令(以分號結尾)表示開啟了一個臨時性的事務;
③接下里就一條條執行事務的DML語句就行了;
⑤遇到commit或者DDL或DCL就會提交本次臨時事務,然後本次臨時事務結束,重新回到自動提交的狀態,如果要想再執行事務那就必須再使用begin或start transaction開啟一個臨時事務!
示例如下:

drop table if exists emp;
create table emp(
emp_id int PRIMARY key,
emp_name VARCHAR(20)
)
START TRANSACTION;-- 開啟一個臨時事務
insert into emp values(2, 'aaa');
insert into emp values(3, 'bbb');
COMMIT; -- 上面的sql組成一個事務,commit之後事務結束
START TRANSACTION;-- 上面的commit之後,又回到了自動提交事物的狀態,這時要執行事務操作的話,要重新開啟一個臨時事務,或者set autocommit=0;
insert into emp values(1, 'aaa');
insert into emp values('c', '123');
COMMIT;

結果如下:
sql執行結果

(5)開啟多個命令列對自動提交模式的影響:由於每個SQL命令列視窗都是一個獨立的連線session,因此相互之間互不影響,在一個命令列視窗中設定了自動提交模式並不會影響其他SQL命令列視窗。

3. 中間點

(1)SQL提供了中間點,允許回滾時不必全部回滾,而是回滾到中間點的位置;
(2)設定中間點的語法是:savepoint 自定義中間點的命名;
(3)中間點肯定是在事務的DML語句中間加入的
(4)考慮到中間點可以設定很多,因此回滾的時候必須制定到哪個中間點上,語法為: rollback to 中間點的名字;

4. JDBC對事務的支援

(1)由JDBC對事物的管理交由Connection, 都是Connection的物件方法實現的;
(2)首先關閉自動提交開啟事務功能:void Connection.setAutoCommit(boolean autoCommit);//false表示關閉自動提交事務功能
(3)當然也可以檢視自動提交功能是否開啟:boolean Connection.getAutoCommit(); //true表示開啟了自動提交事務功能
(4)開啟事務功能後就是執行事務了,事務就是一條條DML語句,因此就是一條條stmt.executeUpdate語句了。
(5)提交任務:
Ⅰ當你執行到第一條DDL/DCL時自動提交(execute一條DDL/DCL語句);
Ⅱ void Connection.commit(); //顯示提交
(6)回滾
Ⅰ如果事務執行過程中丟擲異常則會自動隱式回滾;
Ⅱ顯示回滾:void rollback();
(7)中間點
設定中間點
ⅠSavepoint Connection.setSavepoint();//在事務的某個位置設定一箇中間點,該中間點沒有命名,使用系統預設的命名
Ⅱ Savepoint setSavepoint(String name)//給中間點命名
Ⅲ 回滾到指定的中間點: void Connection.rollback(Savepoint savepoint);//回滾到指定的中間點,回滾到中間點的API就這麼一個,回滾位置是由Savepoint物件指定的,並不是由中間點名稱決定的,因此一般命名的setSavepoint方法不怎麼用,但是那個命名還是可以使用的,那就是必須得到資料庫的命令列中使用rollback to命令才能訪問那個中間點的命名;

示例:事務在執行過程中遇到沒有處理的異常將自動回滾


@Test
public void test3(){
Savepoint sp1 = null;       
try {
conn = C3P0Util.getConnection();
conn.setAutoCommit(false);//設定為手動提交事務
String sql1 = "update emp set emp_name='cc' where emp_id=3";
PreparedStatement pstmt = conn.prepareStatement(sql1);
pstmt.execute();
sp1 = conn.setSavepoint();
String sql2 = "insert into emp values(?,?)";
PreparedStatement pstmt2 = conn.prepareStatement(sql2);
pstmt2.setInt(1, 4);
pstmt2.setString(2, "dd");          
pstmt2.execute();
int a=1/0;//會報錯
System.out.println("出現錯誤後並不會執行下面的conn.commit(),所以這句話不會被列印出來");
conn.commit();
}catch (Exception e) {
try {
conn.rollback(sp1);//出現錯誤之後會回滾到指定的中間點
} catch (SQLException e1) {
throw new RuntimeException(e);
}
}finally{
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}               
}
}

5.事務配合批量更新

(1)為了讓批量更新能正確處理錯誤,應該將整個批處理包裝成一個事務來處理,以便出現意外可以及時的回滾,畢竟批處理的量都比較大,如果出現了問題會導致大量資料不一致和不完整,後果不堪設想,因此一般批處理都要做成事務來執行,示例:

conn = C3P0Util.getConnection();
conn.setAutoCommit(false);
...
Statement stmt = conn.createStatement();
stmt.addBatch(sql1);
stmt.addBatch(sql2);
stmt.addBatch(sql3);
stmt.addBatch(sql4);
...
stmt.executeBatch();//返回int[]表示每個DML語句更新了多少行
conn.commit();
conn.setAutoCommit(true);//還原狀態