1. 程式人生 > >Java開發工程師(Web方向) - 03.數據庫開發 - 第4章.事務

Java開發工程師(Web方向) - 03.數據庫開發 - 第4章.事務

完成 工作單元 ima auto 增加 並發執行 trace driver transfer

第4章--事務

事務原理與開發

事務Transaction:

什麽是事務?

事務是並發控制的基本單位,指作為單個邏輯工作單元執行的一系列操作,且邏輯工作單元需滿足ACID特性。

i.e. 銀行轉賬:開始交易;張三賬戶扣除100元;李四賬戶增加100元;結束交易。

事務的特性:ACID

原子性 Atomicity:整個交易必須作為一個整體來執行。(要麽全部執行,要麽全部不執行)

一致性 Consistency:整個交易總體資金不變

隔離性 Isolation:

case1: 若張三給李四轉賬過程中,趙五給張三轉賬了200元。兩個交易並發執行。

T1 T2

讀取張三余額100;

讀取張三余額100;

給李四轉賬100,

更新張三余額為0;

交易結束 趙五轉入200,

更新張三余額為300

交易結束

case2: 臟讀:張三給別人轉賬100之後張三存錢200,存錢後轉賬由於系統原因失敗回滾。

讀取一個事務未提交的更新

T1 T2

讀取張三余額100

(轉賬) 更新張三余額0

讀取張三余額0

T1 Rollback() (存錢) 更新張三余額200

T2結束(張三賬戶余額為200)

case3: 不可重復讀:同一個事務,兩次讀取同一數值的結果不同,成為不可重復讀。

T1張三讀取自己余額為100;T2讀取張三余額100;T2存錢更新為300;T1張三讀取余額為300。T1中兩次讀取張三余額即為不可重復讀。

case4: 幻讀:兩次讀取的結果包含的行記錄不一樣。

T1讀取所有用戶(張三、李四);T2新增用戶趙五;T1讀取所有用戶(3個);T1/T2結束。T1中兩次讀取的結果中行記錄數不同,稱為幻讀。

需要避免上述cases的產生

隔離性:交易之間相互隔離,在一個交易完成之前,不能受到其他交易的影響

持久性 Durability:整個交易過程一旦結束,無論出現任何情況,交易都應該是永久生效的

使用JDBC進行事務控制:

Connection類中

.setAutoCommit():開啟事務(若為false,則該Connection對象後續的sql都將作為事務來處理;若為true,則該Connection對象後續的所有sql都將作為單獨的語句執行(默認為true))

.commit():事務被提交,即事務生效並結束

.rollback():回滾,回退到事務開始之前的狀態

i.e.

ALTER TABLE user ADD Account int;
UPDATE User SET Account = 100 WHERE id = 1;
UPDATE User SET Account = 0 WHERE id > 1;

技術分享

實現ZhangSi(1)給LiSan(2)轉賬的過程:

(非事務:)

public static void TransferNonTransaction() {
    Connection conn = null;
    PreparedStatement ptmt = null;
    
    try {
        conn = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD);
        String sql = "UPDATE User SET Account = ? WHERE userName = ? AND id = ?;";
        // transfer 100 from ZhangSi(1) to LiSan(2)
        ptmt = conn.prepareStatement(sql);
        ptmt.setInt(1, 0);
        ptmt.setString(2, "ZhangSi");
        ptmt.setInt(3, 1);
        ptmt.execute();

        ptmt.setInt(1, 100);
        ptmt.setString(2, "LiSan");
        ptmt.setInt(3, 2);
        ptmt.execute();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        try {
            if (conn != null) conn.close();
            if (ptmt != null) ptmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

執行完第一個ptmt.execute()後,數據庫中ZhangSi的Account=0, LiSan的Account=0;

出現了一個中間狀態,對於整個業務邏輯的實現是不可接受的。如果此時程序崩潰了將不可挽回。

(事務:)

public static void TransferByTransaction() {
    Connection conn = null;
    PreparedStatement ptmt = null;
    try {
        conn = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD);
        
        // Using Transaction mechanism
        conn.setAutoCommit(false);
        String sql = "UPDATE User SET Account = ? WHERE userName = ? AND id = ?;";
        ptmt = conn.prepareStatement(sql);
        ptmt.setInt(1, 0);
        ptmt.setString(2, "ZhangSi");
        ptmt.setInt(3, 1);
        ptmt.execute();

        ptmt.setInt(1, 100);
        ptmt.setString(2, "LiSan");
        ptmt.setInt(3, 2);
        ptmt.execute();
        
        // Commit the transaction
        conn.commit();
        
    } catch (SQLException e) {
        // if something wrong happens, rolling back
        if(conn != null) {
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }
        e.printStackTrace();
    } finally {
        try {
            if (conn != null) conn.close();
            if (ptmt != null) ptmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

若在第一個ptmt.execute()時斷點,並查詢數據庫,結果為事務執行之前的狀態,並不是中間狀態。

直到conn.commit()方法執行完畢,事務中的所有操作在數據庫中才有效。

Connection類中的檢查點功能:

.setSavePoint():在執行過程中創建保存點,以便rollback()可以回滾到該保存點

.rollback(SavePoint savePoint):回滾到某個檢查點

i.e.

public static void rollbackTest() {
    Connection conn = null;
    PreparedStatement ptmt = null;
    // save point
    Savepoint sp = null;
    try {
        conn = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD);
        
        conn.setAutoCommit(false);
        String sql = "UPDATE User SET Account = ? WHERE userName = ? AND id = ?;";
        ptmt = conn.prepareStatement(sql);
        ptmt.setInt(1, 0);
        ptmt.setString(2, "ZhangSi");
        ptmt.setInt(3, 1);
        ptmt.execute();
        // create a save point
        sp = conn.setSavepoint();

        ptmt.setInt(1, 100);
        ptmt.setString(2, "LiSan");
        ptmt.setInt(3, 2);
        ptmt.execute();

        // throw an exception manually for the purpose of testing
        throw new SQLException();
        
    } catch (SQLException e) {
        // if something wrong happens, rolling back to the save point created before
        // and then transfer the money to Guoyi(3)
        if(conn != null) {
            try {
                conn.rollback(sp);
                System.out.println("Transfer from ZhangSi(1) to LiSan(2) failed;\n"
                        + "Transfer to GuoYi(3) instead");
                
                // other operations
                ptmt.setInt(1, 100);
                ptmt.setString(2, "GuoYi");
                ptmt.setInt(3, 3);
                ptmt.executeQuery();
                conn.commit();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }                     
        }
        
        e.printStackTrace();
    } finally {
        try {
            if (conn != null) conn.close();
            if (ptmt != null) ptmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

事務的隔離級別:4個級別

讀未提交(read uncommited):可能導致臟讀

讀提交(read commited):不可能臟讀,但是會出現不可重復讀

重復讀(repeatable read):不會出現不可重復讀,但是會出現幻讀

串行化(serializable):最高隔離級別,不會出現幻讀,但嚴格的並發控制、串行執行導致數據庫性能差

N.B. 1. 事務隔離級別越高,數據庫性能越差,但對於開發者而言編程難度越低。

2. MySQL默認事務隔離級別為重復讀 repeatable read

JDBC設置隔離級別:

Connection對象中,

.getTransactionIsolation();

.setTransactionIsolation();

死鎖分析與解決

Java開發工程師(Web方向) - 03.數據庫開發 - 第4章.事務