1. 程式人生 > >JDBC事務總結

JDBC事務總結

jdbc事務

系列閱讀

  1. JavaWeb:用JDBC操作資料庫
  2. JavaWeb:JDBC之事務
  3. JavaWeb:JDBC之資料庫連線池
  4. 使用JDBC實現水果超市管理系統

1. 事務

  • 事務的四大特性:ACID
  • mysql中操作事務
  • jdbc中操作事務

1.1 事務概述

為了方便演示事務,我們需要建立一個account表:

CREATE TABLE account(
    id INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(30),
    balance NUMERIC(10.2)
);

INSERT INTO account(NAME,balance) VALUES('zs', 100000);
INSERT INTO account(NAME,balance) VALUES('ls', 100000);
INSERT INTO account(NAME,balance) VALUES('ww', 100000);

SELECT * FROM account;

1.2 什麼是事務

銀行轉賬!張三轉10000塊到李四的賬戶,這其實需要兩條SQL語句:

  • 給張三的賬戶減去10000元
  • 給李四的賬戶加上10000元

如果在第一條SQL語句執行成功後,在執行第二條SQL語句之前,程式被中斷了(可能是丟擲了某個異常,也可能是其他什麼原因),那麼李四的賬戶沒有加上10000元,而張三卻減去了10000元。這肯定是不行的!

你現在可能已經知道什麼是事務了吧!事務中的多個操作,要麼完全成功,要麼完全失敗!不可能存在成功一半的情況!也就是說給張三的賬戶減去10000元如果成功了,那麼給李四的賬戶加上10000元的操作也必須是成功的;否則給張三減去10000元,以及給李四加上10000元都是失敗的!

1.3 事務的四大特性(ACID)

事務的四大特性是:

  • 原子性(Atomicity):事務中所有操作是不可再分割的原子單位。事務中所有操作要麼全部執行成功,要麼全部執行失敗

  • 一致性(Consistency):事務執行後,資料庫狀態與其它業務規則保持一致。如轉賬業務,無論事務執行成功與否,參與轉賬的兩個賬號餘額之和應該是不變的

  • 隔離性(Isolation):隔離性是指在併發操作中,不同事務之間應該隔離開來,使每個併發中的事務不會相互干擾

  • 永續性(Durability):一旦事務提交成功,事務中所有的資料操作都必須被持久化到資料庫中,即使提交事務後,資料庫馬上崩潰,在資料庫重啟時,也必須能保證通過某種機制恢復資料

1.4 MySQL中的事務

在預設情況下,MySQL每執行一條SQL語句,都是一個單獨的事務。如果需要在一個事務中包含多條SQL語句,那麼需要開啟事務和結束事務。

  • 開啟事務:start transaction
  • 結束事務:commit或rollback

在執行SQL語句之前,先執行strat transaction,這就開啟了一個事務(事務的起點),然後可以去執行多條SQL語句,最後要結束事務,commit表示提交,即事務中的多條SQL語句所做出的影響會持久化到資料庫中。或者rollback,表示回滾,即回滾到事務的起點,之前做的所有操作都被撤消了!

下面演示zs給li轉賬10000元的示例:

START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
UPDATE account SET balance=balance+10000 WHERE id=2;
ROLLBACK;

START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
UPDATE account SET balance=balance+10000 WHERE id=2;
COMMIT;

START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
UPDATE account SET balance=balance+10000 WHERE id=2;
quit;

2. JDBC事務

在jdbc中處理事務,都是通過Connection完成的!同一事務中所有的操作,都在使用同一個Connection物件!

2.1、JDBC中的事務

Connection的三個方法與事務相關:

  • setAutoCommit(boolean):設定是否為自動提交事務,如果true(預設值就是true)表示自動提交,也就是每條執行的SQL語句都是一個單獨的事務,如果設定false,那麼就相當於開啟了事務了
  • commit():提交結束事務
  • rollback():回滾結束事務

jdbc處理事務的程式碼格式:

try {
  con.setAutoCommit(false);//開啟事務…
  ….
  …
  con.commit();//try的最後提交事務
} catch() {
  con.rollback();//回滾事務
}

public void transfer(boolean b) {
        Connection con = null;
        PreparedStatement pstmt = null;

        try {
            con = JdbcUtils.getConnection();
            //手動提交
            con.setAutoCommit(false);

            String sql = "update account set balance=balance+? where id=?";
            pstmt = con.prepareStatement(sql);

            //操作
            pstmt.setDouble(1, -10000);
            pstmt.setInt(2, 1);
            pstmt.executeUpdate();

            // 在兩個操作中丟擲異常
            if(b) {
                throw new Exception();
            }

            pstmt.setDouble(1, 10000);
            pstmt.setInt(2, 2);
            pstmt.executeUpdate();

            //提交事務
            con.commit();
        } catch(Exception e) {
            //回滾事務
            if(con != null) {
                try {
                    con.rollback();
                } catch(SQLException ex) {}
            }
            throw new RuntimeException(e);
        } finally {
            //關閉
            JdbcUtils.close(con, pstmt);
        }
    }

3. 儲存點

儲存點是JDBC3.0的東西!當要求資料庫伺服器支援儲存點方式的回滾。 

校驗資料庫伺服器是否支援儲存點!

boolean b = con.getMetaData().supportsSavepoints();

儲存點的作用是允許事務回滾到指定的儲存點位置。在事務中設定好儲存點,然後回滾時可以選擇回滾到指定的儲存點,而不是回滾整個事務!注意,回滾到指定儲存點並沒有結束事務!!!只有回滾了整個事務才算是結束事務了!

Connection類的設定儲存點,以及回滾到指定儲存點方法:

  • 設定儲存點:Savepoint setSavepoint()
  • 回滾到指定儲存點:void rollback(Savepoint)
/*
     * 李四對張三說,如果你給我轉1W,我就給你轉100W。
     * ==========================================
     * 
     * 張三給李四轉1W(張三減去1W,李四加上1W)
     * 設定儲存點!
     * 李四給張三轉100W(李四減去100W,張三加上100W)
     * 檢視李四餘額為負數,那麼回滾到儲存點。
     * 提交事務
     */
    @Test
    public void fun() {
        Connection con = null;
        PreparedStatement pstmt = null;

        try {
            con = JdbcUtils.getConnection();
            //手動提交
            con.setAutoCommit(false);

            String sql = "update account set balance=balance+? where name=?";
            pstmt = con.prepareStatement(sql);

            //操作1(張三減去1W)
            pstmt.setDouble(1, -10000);
            pstmt.setString(2, "zs");
            pstmt.executeUpdate();

            //操作2(李四加上1W)
            pstmt.setDouble(1, 10000);
            pstmt.setString(2, "ls");
            pstmt.executeUpdate();

            // 設定儲存點
            Savepoint sp = con.setSavepoint();

            //操作3(李四減去100W)
            pstmt.setDouble(1, -1000000);
            pstmt.setString(2, "ls");
            pstmt.executeUpdate();      

            //操作4(張三加上100W)
            pstmt.setDouble(1, 1000000);
            pstmt.setString(2, "zs");
            pstmt.executeUpdate();

            //操作5(檢視李四餘額)
            sql = "select balance from account where name=?";
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, "ls");
            ResultSet rs = pstmt.executeQuery();
            rs.next();
            double balance = rs.getDouble(1);
      //如果李四餘額為負數,那麼回滾到指定儲存點
            if(balance < 0) {
                con.rollback(sp);
                System.out.println("張三,你上當了!");
            }

            //提交事務
            con.commit();
        } catch(Exception e) {
            //回滾事務
            if(con != null) {
                try {
                    con.rollback();
                } catch(SQLException ex) {}
            }
            throw new RuntimeException(e);
        } finally {
            //關閉
            JdbcUtils.close(con, pstmt);
        }
    }

4. 事務隔離級別

4.1 事務的併發讀問題

  • 髒讀:讀取到另一個事務未提交資料
  • 不可重複讀:兩次讀取不一致
  • 幻讀(虛讀):讀到另一事務已提交資料

4.2 併發事務問題

因為併發事務導致的問題大致有5類,其中兩類是更新問題,三類是讀問題

  • 髒讀(dirty read):讀到另一個事務的未提交更新資料,即讀取到了髒資料
  • 不可重複讀(unrepeatable read):對同一記錄的兩次讀取不一致,因為另一事務對該記錄做了修改
  • 幻讀(虛讀)(phantom read):對同一張表的兩次查詢不一致,因為另一事務插入了一條記錄

4.2.1 髒讀

事務1:張三給李四轉賬100元 
事務2:李四檢視自己的賬戶

  • t1:事務1:開始事務
  • t2:事務1:張三給李四轉賬100元
  • t3:事務2:開始事務
  • t4:事務2:李四檢視自己的賬戶,看到賬戶多出100元(髒讀)
  • t5:事務2:提交事務
  • t6:事務1:回滾事務,回到轉賬之前的狀態

4.2.2不可重複讀

事務1:酒店檢視兩次1048號房間狀態 
事務2:預訂1048號房間

  • t1:事務1:開始事務
  • t2:事務1:檢視1048號房間狀態為空閒
  • t3:事務2:開始事務
  • t4:事務2:預定1048號房間
  • t5:事務2:提交事務
  • t6:事務1:再次檢視1048號房間狀態為使用
  • t7:事務1:提交事務 
    對同一記錄的兩次查詢結果不一致!

4.2.3幻讀

事務1:對酒店房間預訂記錄兩次統計 
事務2:新增一條預訂房間記錄

  • t1:事務1:開始事務
  • t2:事務1:統計預訂記錄100條
  • t3:事務2:開始事務
  • t4:事務2:新增一條預訂房間記錄
  • t5:事務2:提交事務
  • t6:事務1:再次統計預訂記錄為101記錄
  • t7:事務1:提交

對同一表的兩次查詢不一致!

不可重複讀和幻讀的區別:

  • 不可重複讀是讀取到了另一事務的更新;
  • 幻讀是讀取到了另一事務的插入(MySQL中無法測試到幻讀);

4.3 四大隔離級別

4個等級的事務隔離級別,在相同資料環境下,使用相同的輸入,執行相同的工作,根據不同的隔離級別,可以導致不同的結果。不同事務隔離級別能夠解決的資料併發問題的能力是不同的

4.3.1 SERIALIZABLE(序列化)

  • 不會出現任何併發問題,因為它是對同一資料的訪問是序列的,非併發訪問的
  • 效能最差

4.3.2 REPEATABLE READ(可重複讀)(MySQL)

  • 防止髒讀和不可重複讀,不能處理幻讀問題
  • 效能比SERIALIZABLE好

4.3.3 READ COMMITTED(讀已提交資料)(Oracle)

  • 防止髒讀,沒有處理不可重複讀,也沒有處理幻讀;
  • 效能比REPEATABLE READ好

4.3.4 READ UNCOMMITTED(讀未提交資料)

  • 可能出現任何事務併發問題
  • 效能最好

MySQL的預設隔離級別為REPEATABLE READ,這是一個很不錯的選擇吧!

4.3.5 MySQL隔離級別

MySQL的預設隔離級別為Repeatable read,可以通過下面語句檢視:

select @@tx_isolation

也可以通過下面語句來設定當前連線的隔離級別:

set transaction isolationlevel [41]

4.3.6 JDBC設定隔離級別

con. setTransactionIsolation(int level);引數可選值如下:

  • Connection.TRANSACTION_READ_UNCOMMITTED
  • Connection.TRANSACTION_READ_COMMITTED
  • Connection.TRANSACTION_REPEATABLE_READ
  • Connection.TRANSACTION_SERIALIZABLE

5. 事務總結

  • 事務的特性:ACID
  • 事務開始邊界與結束邊界:開始邊界(con.setAutoCommit(false)),結束邊界(con.commit()或con.rollback())
  • 事務的隔離級別: READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。多個事務併發執行時才需要考慮併發事務