事務的概念
事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功。
例如:A向B轉賬100元,對應於如下兩條sql語句:
update from account set money=money+100 where name='b';
update from account set money=money-100 where name='a';
數據庫默認事務是自動提交的,也就是發一條sql它就執行一條,如果想多條sql放在一個事務中執行,則需要使用如下語句:
start transaction
…
…
commit
數據庫開啓事務命令:
start transaction :開啓事務
rollback:回滾事務
commit:提交事務
MySQL數據庫中操作事務命令
編寫測試SQL腳本,如下:
/* 創建數據庫 */
create database day16;
use day16;
/* 創建賬戶表 */
create table account
(
id int primary key auto_increment,
name varchar(40),
money float
) character set utf8 collate utf8_general_ci;
/* 插入測試數據 */
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);
下面我們在MySQL數據庫中模擬aaa向bbb轉帳這個業務場景。
開啓事務(start transaction)
使用”start transaction”開啓MySQL數據庫的事務,如下所示:
我們首先在數據庫中模擬轉賬失敗的場景,首先執行update語句讓aaa用戶的money減少100塊錢,如下圖所示:
現在假設程序拋出異常,也即該鏈接斷了,代碼塊沒有完成,此時數據庫會自動回滾掉此sql語句造成的影響,也就是說這條sql語句沒有執行。我們現在就來模擬這種情況,我們關閉當前操作的dos命令行窗口,這樣就導致了剛纔執行的update語句的數據庫的事務沒有被提交,那麼我們對aaa用戶的修改就不算是真正的修改了,下次在查詢aaa用戶的money時,依然還是之前的1000,如下圖所示:
提交事務(commit)
下面我們在數據庫模擬aaa向bbb轉賬成功的場景。
我們手動提交(commit)數據庫事務之後,aaa向bbb轉賬100塊錢的這個業務操作算是真正成功了,aaa賬戶中少了100,bbb賬戶中多了100。
回滾事務(rollback)
通過手動回滾事務,讓所有的操作都失效,這樣數據就會回到最初的初始狀態!
JDBC中使用事務
當Jdbc程序向數據庫獲得一個Connection對象時,默認情況下這個Connection對象會自動向數據庫提交在它上面發送的SQL語句。若想關閉這種默認提交方式,讓多條SQL在一個事務中執行,可使用下列的JDBC控制事務語句:
Connection.setAutoCommit(false); //開啓事務(start transaction)
Connection.rollback(); //回滾事務(rollback)
Connection.commit(); //提交事務(commit)
JDBC使用事務範例
在JDBC代碼中演示銀行轉帳案例,使如下轉帳操作在同一事務中執行:
update from account set money=money-100 where name=‘aaa’;
update from account set money=money+100 where name=‘bbb’;
模擬aaa向bbb轉賬成功時的業務場景
public class Demo1 {
/*
* a--->b轉100元
*/
public static void main(String[] args) throws SQLException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false); // 相當於start transaction,開啓事務
String sql1 = "update account set money=money-100 where name='aaa'";
String sql2 = "update account set money=money+100 where name='bbb'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
st = conn.prepareStatement(sql2);
st.executeUpdate();
conn.commit();
} finally {
JdbcUtils.release(conn, st, rs);
}
}
}
模擬aaa向bbb轉賬過程中出現異常導致有一部分SQL執行失敗後讓數據庫自動回滾事務
public class Demo1 {
/*
* a--->b轉100元
*/
public static void main(String[] args) throws SQLException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false); // 相當於start transaction,開啓事務
String sql1 = "update account set money=money-100 where name='aaa'";
String sql2 = "update account set money=money+100 where name='bbb'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
int x = 1/0; // 程序運行到這個地方拋異常,後面的代碼就不執行,數據庫沒有收到commit命令
st = conn.prepareStatement(sql2);
st.executeUpdate();
conn.commit();
} finally {
JdbcUtils.release(conn, st, rs);
}
}
}
模擬aaa向bbb轉賬過程中出現異常導致有一部分SQL執行失敗時手動通知數據庫回滾事務
public class Demo1 {
/*
* a--->b轉100元
*/
public static void main(String[] args) throws SQLException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false); // 相當於start transaction,開啓事務
String sql1 = "update account set money=money-100 where name='aaa'";
String sql2 = "update account set money=money+100 where name='bbb'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
int x = 1/0; // 程序運行到這個地方拋異常,後面的代碼就不執行,數據庫沒有收到commit命令
st = conn.prepareStatement(sql2);
st.executeUpdate();
conn.commit();
} catch (Exception e) {
e.printStackTrace();
conn.rollback(); // 捕獲到異常之後手動通知數據庫執行回滾事務的操作
} finally {
JdbcUtils.release(conn, st, rs);
}
}
}
設置事務回滾點
在開發中,有時候可能需要手動設置事務的回滾點,在JDBC中使用如下的語句設置事務回滾點:
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);
Conn.commit(); // 回滾後必須通知數據庫提交事務
設置事務回滾點範例:
public class Demo2 {
// 事務回滾點概念
public static void main(String[] args) throws SQLException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
Savepoint sp = null;
try {
conn = JdbcUtils.getConnection(); // MySQL默認的隔離級別——REPEATABLE-READ,並且是嚴格遵循數據庫規範設計的,即支持4種隔離級別
// Oracle默認的隔離級別——Read committed,並且不支持這4種隔離級別,只支持這4種隔離級別中的2種,Read committed和Serializable
// conn.setTransactionIsolation(); // 相當於設置CMD窗口的隔離級別
conn.setAutoCommit(false); // 相當於start transaction,開啓事務
// 不符合實際需求
String sql1 = "update account set money=money-100 where name='aaa'";
String sql2 = "update account set money=money+100 where name='bbb'";
String sql3 = "update account set money=money+100 where name='ccc'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
/*
* 只希望回滾掉這一條sql語句,上面那條sql語句讓其執行成功
* 這時可設置事務回滾點
*/
sp = conn.setSavepoint();
st = conn.prepareStatement(sql2);
st.executeUpdate();
int x = 1/0; // 程序運行到這個地方拋異常,後面的代碼就不執行,數據庫沒有收到commit命令
st = conn.prepareStatement(sql3);
st.executeUpdate();
conn.commit();
} catch (Exception e) {
e.printStackTrace();
conn.rollback(sp); // 回滾到sp點,sp點上面的sql語句發給數據庫執行,由於數據庫沒收到commit命令,數據庫又會自動將這條sql語句的影響回滾掉,所以回滾完,一定要記得commit命令。
conn.commit(); // 手動回滾後,一定要記得提交事務
} finally {
JdbcUtils.release(conn, st, rs);
}
}
}