什麼是事務

事務的概念

事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功。
例如: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);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章