什麼是事務
資料庫中一些操作的集合是一個獨立的單元,事務就是構成單一邏輯工作機關的集合。
為什麼需要事務
事務是為解決資料安全操作提出的,事務控制實際上就是控制資料的安全通路。
比如:銀行轉帳業務,賬戶A給賬戶B轉帳100元,需要賬戶A餘額減100元,賬戶B餘額加100元,兩個需要同時發生。完成這種操作需要保證要麼全部成功,要麼全部失敗。
什麼是復原
未能成功完成的事務成為中止事務,對中止事務造成的變更需要進行撤銷處理,稱為事務復原。
事務的特性(ACID 原則)
- 原子性(atomicity):對事務中的全部操作是不可分割的,要麼全部完成,要麼都不執行。
- 一緻性(consistency):事務執行之前和執行之後,資料庫都必須處于一緻性狀态。
- 隔離性(isolation):事務的執行不受其他事務的幹擾,事務執行的中間結果對其他事務必須是透明的。
- 持久性(durability):對于任意已送出的事務,系統必須保證該對資料庫的改變不丢失,即使資料庫出現故障。
Java JDBC 事務機制
比如有個業務:當我們修改一個資訊後再去查詢這個資訊。這是一個簡單的業務,實作起來也非常容易,但是當這個業務放在多線程高并發的平台下,問題自然就出現了。
比如當執行了一個修改後,在查詢之前有一個線程也執行了修改語句,這時再執行查詢,看到的資訊就有可能和我們修改的不同。為了解決這一問題,就引入了引入JDBC事務機制。
如何操作
把事務操作設定為不自動送出,通過手動送出就能實作事務的處理。
搭建實驗環境實驗,在目前資料庫中建立一個測試表:
CREATE TABLE tb (
id INT UNSIGNED NOT NULL AUTO_INCREMENT KEY COMMENT '編号',
name VARCHAR(20) NOT NULL COMMENT '姓名',
sex VARCHAR(4) NOT NULL COMMENT '性别',
phone VARCHAR(11) NOT NULL COMMENT '手機号碼'
);
插入幾條測試資料:
INSERT INTO tb
(name, sex, phone)
VALUES
("張三", "男", "139****2234"),
("李四", "女", "130****3239"),
("王二", "男", "136****1234"),
("小王", "女", "137****1735"),
("趙雲", "男", "131****1255"),
("關羽", "男", "139****1930");
實驗代碼 相關延伸
事務并發處理可能引起的問題
- 髒讀(dirty read):一個事務讀取了另一個事務尚未送出的資料。
- 不可重複讀(non-repeatable read):一個事務的操作導緻另一個事務前後兩次讀取到不同的資料。
- 幻讀(phantom read):一個事務的操作導緻另一個事務前後兩次查詢到的結果資料量不同。
舉例:
- 事務A、B并發執行,當A事務update後,B事務select讀取到A尚未送出的資料,此時A事務rollback,則B讀取到的資料是無效的“髒”資料。
- 當B事務select讀取資料後,A事務update操作更改B事務select到的資料,此時B事務再次讀取該資料,發現前後兩次的資料不一樣。
- 當B事務select讀取資料後,A事務insert或delete了一條滿足A事務的select條件的記錄,此時B事務再次select,發現查詢到前不存在的資料(“幻影”),或者前面的某個記錄不見了。
JDBC的事務支援
JDBC對事務的支援展現在三個方面:
1.自動送出模式(Auto-commit mode)
Connection提供了一個
auto-commit
的屬性來指定事務何時結束。
- a.當auto-commit為
時,當每個獨立的SQL操作執行完畢,事務立即自動送出,也就是說每個SQL操作都是一個事務的。auto-commit預設為true
。true
一個獨立SQL操作什麼時候什麼執行完畢呢?
JDBC規範這樣規定
對資料操作語言(DML如insert,update,delete)和資料定義語言(DDL如create,drop),語句一執行完就視為執行完畢。
對select語句,當與它關聯的ResultSet對象關閉時,視為執行完畢。
對存儲過程或者其他傳回多個結果的語句,當與它關聯的所有ResultSet對象全部關閉,所有update count(update,delete等語句操作影響的行數)和output parameter(存儲過程的輸出參數)都已經擷取之後,視為執行完畢。
- b.當auto-commit為
時,每個事務都必須顯式調用false
方法進行送出,或者顯式調用commit方法進行復原。commit
2.事務隔離級别(Transaction Isolation Levels)
JDBC提供了5種不同的事務隔離級别。在Connection中進行了定義。
-
:JDBC不支援事務TRANSACTION_NONE
-
:允許髒讀、不可重複讀和幻讀TRANSACTION_READ_UNCOMMITTED
-
:禁止髒讀,但允許不可重複讀和幻讀TRANSACTION_READ_COMMITTED
-
:禁止髒讀和不可重複讀,單可以幻讀TRANSACTION_REPEATABLE_READ
-
:禁止髒讀、不可重複讀和幻讀TRANSACTION_SERIALIZABLE
3.儲存點(SavePoint)
JDBC定義了SavePoint接口,提供在一個更細粒度的事務控制機制。當設定了一個儲存點後,可以rollback到該儲存點處的狀态,而不是rollback整個事務。
連接配接對象擷取和關閉的時機
現狀:
- 連接配接對象的擷取和銷毀比較浪費時間
- 一個事務中多個操作,若每個操作都生成一個連接配接對象,多使用者同時連接配接資料庫時,通路效率會非常的低。
需求:
- 一個事務中的多個操作應該用同一個連接配接對象控制,不然無法實作送出和復原。一個事務開始時獲得連接配接對象,一個事務結束時關閉連接配接。
解決:
-
将連接配接對象的申請交給專門的連接配接管理類
事務申請連接配接對象時,通過連接配接管理類申請,而不再通過DriverManager申請
- 一個事務一定是由一個線程完成的,使用線程局部變量獲得同一個連接配接對象
接口 DataSource
DataSource
該工廠用于提供到此 DataSource 對象所表示的實體資料源的連接配接。
作為 DriverManager 工具的替代項,DataSource 對象是擷取連接配接的首選方法。
DataSource
接口由驅動程式供應商實作。共有三種類型的實作:
- 基本實作:生成标準的 Connection 對象
- 連接配接池實作:生成自動參與連接配接池的 Connection 對象。此實作與中間層連接配接池管理器一起使用。
- 分布式事務實作:生成一個 Connection 對象,該對象可用于分布式事務,大多數情況下總是參與連接配接池。此實作與中間層事務管理器一起使用,大多數情況下總是與連接配接池管理器一起使用。
方法:
-
:嘗試建立與此 DataSource 對象所表示的資料源的連接配接。getConnection()
-
getConnection(String username, String password)
執行個體:
先在src目錄下建立配置檔案
config.properties
:
driver=com.mysql.jdbc.Driver
url=jdbc\:mysql\://127.0.0.1\:3306/empmgs?useUnicode\=true&characterEncoding\=utf-8
username=root
password=root
執行個體代碼:
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
public class demo4 {
static Properties prop = new Properties();
static DataSource ds = null;
static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
private static Connection conn = null;
private static Statement sm = null;
/**
* 靜态初始化塊加載注冊驅動
*/
static {
try {
prop.load(new FileInputStream("src/config.properties"));
ds = BasicDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
e.printStackTrace();
}
}
static public Connection getConnection(){
try {
//先看線程局部變量
conn = threadLocal.get();
if(conn == null){//線程局部變量中沒有儲存連接配接對象
conn = ds.getConnection();
threadLocal.set(conn);//設定連接配接對象到線程局部變量
}
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
public static void main(String[] args) {
try{
conn = getConnection ();
sm = conn.createStatement();
// 關閉自動送出
conn.setAutoCommit(false);
sm.executeUpdate("UPDATE tb SET name = '老王' WHERE id = 3 ");
sm.executeUpdate("INSERT INTO tb (name, sex, phone) VALUES ('美麗','女','132****5555')");
conn.commit();
System.out.println("運作結束");
} catch (Exception e) {
try {
conn.rollback();
} catch (Exception ex) {
e.printStackTrace();
}
} finally {
if (sm != null) {
try {
sm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
conn = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}