1. 程式人生 > 實用技巧 >2020-11-24

2020-11-24

此文轉載自:https://blog.csdn.net/qq_43625140/article/details/110084389#commentBox

1. 資料庫事務

1.1 是什麼

什麼是事務,可以閱讀我寫的有關資料庫 事務 這篇文章。從事務的四大特性來說,事務機制是為了保證資料的一致性,事務是最小的執行單元,要麼全部成功要麼全部不成功。

1.2 為什麼

眾所周知,SQL語言是一種操作資料庫的非過程化語言,無疑它的功能是很強大的。比如一個小小的select語法結構就可以查詢到我們想要的任何資料,多麼簡潔方便。

然而業務是複雜的,一條SQL並不能滿足我們的需求,往往一個業務的執行需要多條SQL語句的配合使用來完成。一個寫操作會改變資料庫的資料,多個寫操作如果不能保證都成功那麼資料必然不是完整的。

為了保證資料的完整性就必須保證多條SQL語句的執行要麼都成功要麼都不成功,因此引出資料庫事務機制。事務能夠保證我們在解決複雜業務時,某一步出錯不會造成資料的丟失或者增加。

轉賬案例

  • 例如現在有李白和杜甫兩個人的存款在資料庫中存在記錄,他們各有500元的存款如下:

  • 李白要向杜甫轉賬200元,那我們必須修改兩個人的存款,李白存款減200,杜甫存款加200,SQL如下:
# 李白存款減200
update `user` set money = money - 200 where name = '李白';

# 杜甫存款加200
update `user` set money = money + 200 where name = '杜甫';
  • 針對這個轉賬業務我們需要使用兩條更新語句來完成,然而我們的程式只能先執行完一條再執行另一條,也就是說程式執行需要一個過程。
  • 如果在執行 李白存款減200後,杜甫存款加200前
    這個極其短暫的時間空隙時出現了故障,導致我們只執行了減款操作而沒有執行加款操作,那麼200元豈不是不翼而飛。

  • 李白和杜甫就不會打起來嗎?李白說:“我轉了,因為我的存款少了200元”,杜甫說:“你沒轉,因為我的存款沒有多200元”。系統無緣無故吞了200元,現在誰還敢存錢。
  • 問題在於兩條SQL語句我們只成功執行了一條SQL語句,導致資料庫的金額資料總量少了200元,這樣直接影響了資料的一致性。為了避免出現這樣的情況,引入事務機制,將多條SQL語句加持在一個最小執行單元中,執行成功則業務成功,執行失敗就貌似我們沒有執行過,資料不會發生丟失或增加。

2. JDBC事務支援

JDBC作為java連線資料庫的API自然也提供了相關方法來操作事務:

  • connection.setTransactionIsolation(int level):設定事務隔離級別

  • connection.setAutoCommit(boolean ):設定是否自動提交事務,預設自動提交,類似於是否開啟事務。

  • connection.setSavepoint(String):設定儲存點

  • connection.commit():提交事務

  • connection.rollback():回滾事務,預設回滾到最初,也可以指定回滾到指定儲存點。

3. JDBC事務案例

基於李白向杜甫轉賬的例子,我們通過JDBC來進行操作,探究事務是否可以被控制住。

3.1 資料庫環境

# 新建使用者表
CREATE TABLE IF NOT EXISTS `user`(
   `pk_id` INT AUTO_INCREMENT,
   `name` VARCHAR(20) NOT NULL,
   `money` DOUBLE,
   CONSTRAINT `pk_user` PRIMARY KEY(`pk_id`)
);

# 新增使用者資料
INSERT INTO 
  `user`(`name`, `money`)
VALUES
  ('李白', 500.0),
  ('杜甫', 500.0);

3.2 建立專案

  • 通過IDEA基於Maven構建普通Java工程

3.3 新增依賴

<!-- 依賴管理 -->
<dependencies>
    <!-- mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    <!-- junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.1</version>
    </dependency>
</dependencies>

3.4 轉賬異常

@Test
public void test01() throws ClassNotFoundException, SQLException
{
    // 定義連線引數
    String driverName = "com.mysql.jdbc.Driver";
    String url = "jdbc:mysql://127.0.0.1:3306/zw_test";
    String username = "root";
    String password = "root";

    // 1.註冊驅動
    Class.forName(driverName);

    // 2.獲取連線
    Connection connection = DriverManager.getConnection(url, username, password);

    // 3.增刪改查
    Statement statement = connection.createStatement();

    // 李白扣錢
    String sql1 = "update user set money = money - 200 where name = '李白'";
    statement.executeUpdate(sql1);

    // 模擬發生意外
    int i = 1/0;

    // 杜甫加錢
    String sql2 = "update user set money = money + 200 where name = '杜甫'";
    statement.executeUpdate(sql2);

    // 4.關閉資源
    statement.close();
    connection.close();
}
  • 轉賬前

  • 轉賬後

3.5 轉賬正常

@Test
public void test02() throws ClassNotFoundException, SQLException
{
    // 定義連線引數
    String driverName = "com.mysql.jdbc.Driver";
    String url = "jdbc:mysql://127.0.0.1:3306/zw_test";
    String username = "root";
    String password = "root";

    // 1.註冊驅動
    Class.forName(driverName);

    Connection connection = null;
    Statement statement = null;

    try
    {
        // 2.獲取連線
        connection = DriverManager.getConnection(url, username, password);

        // 3.增刪改查
        
        // 關閉事務的自動提交
        connection.setAutoCommit(false);
        
        // 獲取操作物件
        statement = connection.createStatement();

        // 李白扣錢
        String sql1 = "update user set money = money - 200 where name = '李白'";
        statement.executeUpdate(sql1);

        // 模擬發生意外
        int i = 1/0;

        // 杜甫加錢
        String sql2 = "update user set money = money + 200 where name = '杜甫'";
        statement.executeUpdate(sql2);
        
        // 提交事務
        connection.commit();
    }
    catch (Exception e)
    {
        e.printStackTrace();
        
        // 發生異常,回滾事務
        connection.rollback();
    }
    finally
    {
        // 4.關閉資源
        statement.close();
        connection.close();
    }
}
  • 轉賬前

  • 轉賬後