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元,現在誰還敢存錢。
- 問題在於兩條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();
}
}
- 轉賬前
- 轉賬後