spring 事務(wù)管理機(jī)制 - 基本概念

事務(wù)的四種特性(ACID)

事務(wù)具備 ACID 四種特性,ACID 是 Atomic(原子性)、Consistency(一致性)、Isolation(隔離性)和 Durability(持久性)的英文縮寫(xiě)。

原子性(Atomicity)

事務(wù)最基本的操作單元,要么全部成功,要么全部失敗,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過(guò)程中發(fā)生錯(cuò)誤,會(huì)被回滾到事務(wù)開(kāi)始前的狀態(tài),就像這個(gè)事務(wù)從來(lái)沒(méi)有執(zhí)行過(guò)一樣。

一致性(Consistency)

事務(wù)的一致性指的是在一個(gè)事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫(kù)都必須處于一致性狀態(tài)。如果事務(wù)成功地完成,那么系統(tǒng)中所有變化將正確地應(yīng)用,系統(tǒng)處于有效狀態(tài)。如果在事務(wù)中出現(xiàn)錯(cuò)誤,那么系統(tǒng)中的所有變化將自動(dòng)地回滾,系統(tǒng)返回到原始狀態(tài)。

隔離性(Isolation)

指的是在并發(fā)環(huán)境中,當(dāng)不同的事務(wù)同時(shí)操縱相同的數(shù)據(jù)時(shí),每個(gè)事務(wù)都有各自的完整數(shù)據(jù)空間。由并發(fā)事務(wù)所做的修改必須與任何其他并發(fā)事務(wù)所做的修改隔離。事務(wù)查看數(shù)據(jù)更新時(shí),數(shù)據(jù)所處的狀態(tài)要么是另一事務(wù)修改它之前的狀態(tài),要么是另一事務(wù)修改它之后的狀態(tài),事務(wù)不會(huì)查看到中間狀態(tài)的數(shù)據(jù)。

持久性(Durability)

指的是只要事務(wù)成功結(jié)束,它對(duì)數(shù)據(jù)庫(kù)所做的更新就必須永久保存下來(lái)。即使發(fā)生系統(tǒng)崩潰,重新啟動(dòng)數(shù)據(jù)庫(kù)系統(tǒng)后,數(shù)據(jù)庫(kù)還能恢復(fù)到事務(wù)成功結(jié)束時(shí)的狀態(tài)。

TransactionDefinition - 五個(gè)隔離級(jí)別

ISOLATION_DEFAULT

這是一個(gè) PlatformTransactionManager 默認(rèn)的隔離級(jí)別,使用數(shù)據(jù)庫(kù)默認(rèn)的事務(wù)隔離級(jí)別。另外四個(gè)與 JDBC 的隔離級(jí)別相對(duì)應(yīng);

ISOLATION_READ_UNCOMMITTED

這是事務(wù)最低的隔離級(jí)別,它充許別外一個(gè)事務(wù)可以看到這個(gè)事務(wù)未提交的數(shù)據(jù)。這種隔離級(jí)別會(huì)產(chǎn)生臟讀,不可重復(fù)讀和幻讀。

ISOLATION_READ_COMMITTED

保證一個(gè)事務(wù)修改的數(shù)據(jù)提交后才能被另外一個(gè)事務(wù)讀取。另外一個(gè)事務(wù)不能讀取該事務(wù)未提交的數(shù)據(jù)。這種事務(wù)隔離級(jí)別可以避免臟讀出現(xiàn),但是可能會(huì)出現(xiàn)不可重復(fù)讀和幻像讀。

ISOLATION_REPEATABLE_READ

這種事務(wù)隔離級(jí)別可以防止臟讀,不可重復(fù)讀。但是可能出現(xiàn)幻讀。它除了保證一個(gè)事務(wù)不能讀取另一個(gè)事務(wù)未提交的數(shù)據(jù)外,還保證了避免下面的情況產(chǎn)生(不可重復(fù)讀)。

ISOLATION_SERIALIZABLE

這是花費(fèi)最高代價(jià)但是最可靠的事務(wù)隔離級(jí)別。事務(wù)被處理為順序執(zhí)行。除了防止臟讀,不可重復(fù)讀外,還避免了幻讀。

事務(wù)的并發(fā)問(wèn)題

Dirty reads - 臟讀

指當(dāng)一個(gè)事務(wù)正字訪問(wèn)數(shù)據(jù),并且對(duì)數(shù)據(jù)進(jìn)行了修改,而這種數(shù)據(jù)還沒(méi)有提交到數(shù)據(jù)庫(kù)中,這時(shí),另外一個(gè)事務(wù)也訪問(wèn)這個(gè)數(shù)據(jù),然后使用了這個(gè)數(shù)據(jù)。因?yàn)檫@個(gè)數(shù)據(jù)還沒(méi)有提交那么另外一個(gè)事務(wù)讀取到的這個(gè)數(shù)據(jù)我們稱之為臟數(shù)據(jù)。依據(jù)臟數(shù)據(jù)所做的操作肯能是不正確的。

non-repeatable reads - 不可重復(fù)讀

指在一個(gè)事務(wù)內(nèi),多次讀同一數(shù)據(jù)。在這個(gè)事務(wù)還沒(méi)有執(zhí)行結(jié)束,另外一個(gè)事務(wù)也訪問(wèn)該同一數(shù)據(jù),那么在第一個(gè)事務(wù)中的兩次讀取數(shù)據(jù)之間,由于第二個(gè)事務(wù)的修改第一個(gè)事務(wù)兩次讀到的數(shù)據(jù)可能是不一樣的,這樣就發(fā)生了在一個(gè)事物內(nèi)兩次連續(xù)讀到的數(shù)據(jù)是不一樣的,這種情況被稱為是不可重復(fù)讀。

phantom reads - 幻讀

這個(gè)和 non-repeatable reads 相似,也是同一個(gè)事務(wù)中多次讀不一致的問(wèn)題。但是 non-repeatable reads 的不一致是因?yàn)樗〉臄?shù)據(jù)集被改變了(比如 total 的數(shù)據(jù)),但是 phantom reads 所要讀的數(shù)據(jù)的不一致卻不是他所要讀的數(shù)據(jù)集改變,而是他的條件數(shù)據(jù)集改變。比如 Select account.id where account.name="ppgogo*",第一次讀取了 6 個(gè)符合條件的 id,第二次讀取的時(shí)候,由于事務(wù) b 把一個(gè)帳號(hào)的名字由"dd"改成"ppgogo1",結(jié)果取出來(lái)了 7 個(gè)數(shù)據(jù)。概括:一個(gè)事務(wù)先后讀取一個(gè)范圍的記錄,但兩次讀取的紀(jì)錄數(shù)不同,我們稱之為幻象讀。

TransactionDefinition - 七個(gè)事務(wù)傳播行為:

PROPAGATION_REQUIRED

如果存在一個(gè)事務(wù),則支持當(dāng)前事務(wù)。如果沒(méi)有事務(wù)則開(kāi)啟一個(gè)新的事務(wù)。

// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
    ……
    methodB();
    ……
}
// 事務(wù)屬性 PROPAGATION_REQUIRED
methodB{……}

使用 spring 聲明式事務(wù),spring 使用 AOP 來(lái)支持聲明式事務(wù),會(huì)根據(jù)事務(wù)屬性,自動(dòng)在方法調(diào)用之前決定是否開(kāi)啟一個(gè)事務(wù),并在方法執(zhí)行之后決定事務(wù)提交或回滾事務(wù)。

單獨(dú)調(diào)用 methodB 方法相當(dāng)于:

Main { 
    Connection con=null; 
    try{ 
        con = getConnection(); 
        con.setAutoCommit(false); 
        // 方法調(diào)用
        methodB(); 
        // 提交事務(wù)
        con.commit(); 
    } catch(RuntimeException ex){ 
        // 回滾事務(wù)
        con.rollback();   
    } finally { 
        // 釋放資源
        closeCon(); 
    } 
} 

Spring 保證在 methodB 方法中所有的調(diào)用都獲得到一個(gè)相同的連接。在調(diào)用 methodB 時(shí),沒(méi)有一個(gè)存在的事務(wù),所以獲得一個(gè)新的連接,開(kāi)啟了一個(gè)新的事務(wù)。
單獨(dú)調(diào)用 MethodA 時(shí),在 MethodA 內(nèi)又會(huì)調(diào)用 MethodB,執(zhí)行效果相當(dāng)于:

    public static void main(String[] args) {
        Connection con = null;
        try {
            con = getConnection();
            methodA();
            con.commit();
        } catch (RuntimeException ex) {
            con.rollback();
        } finally {
            closeCon();
        }
    }

調(diào)用 MethodA 時(shí),環(huán)境中沒(méi)有事務(wù),所以開(kāi)啟一個(gè)新的事務(wù).當(dāng)在 MethodA 中調(diào)用 MethodB 時(shí),環(huán)境中已經(jīng)有了一個(gè)事務(wù),所以 methodB 就加入當(dāng)前事務(wù)。

PROPAGATION_SUPPORTS

如果存在一個(gè)事務(wù),支持當(dāng)前事務(wù)。如果沒(méi)有事務(wù),則非事務(wù)的執(zhí)行。但是對(duì)于事務(wù)同步的事務(wù)管理器,PROPAGATION_SUPPORTS 與不使用事務(wù)有少許不同。

// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
    ……
    methodB();
    ……
}
// 事務(wù)屬性 PROPAGATION_SUPPORTS
methodB{……}

單純的調(diào)用 methodB 時(shí),methodB 方法是非事務(wù)的執(zhí)行的。當(dāng)調(diào)用 methdA 時(shí),methodB 則加入了 methodA 的事務(wù)中,事務(wù)地執(zhí)行。

PROPAGATION_MANDATORY

如果已經(jīng)存在一個(gè)事務(wù),支持當(dāng)前事務(wù)。如果沒(méi)有一個(gè)活動(dòng)的事務(wù),則拋出異常。

// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
    ……
    methodB();
    ……
}
// 事務(wù)屬性 PROPAGATION_MANDATORY
methodB{……}

當(dāng)單獨(dú)調(diào)用 methodB 時(shí),因?yàn)楫?dāng)前沒(méi)有一個(gè)活動(dòng)的事務(wù),則會(huì)拋出異常 throw new IllegalTransactionStateException("Transaction propagation 'mandatory' but no existing transaction found");當(dāng)調(diào)用 methodA 時(shí),methodB 則加入到 methodA 的事務(wù)中,事務(wù)地執(zhí)行。

PROPAGATION_REQUIRES_NEW

總是開(kāi)啟一個(gè)新的事務(wù)。如果一個(gè)事務(wù)已經(jīng)存在,則將這個(gè)存在的事務(wù)掛起。

// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
    ……
    methodB();
    ……
}
// 事務(wù)屬性 PROPAGATION_REQUIRES_NEW
methodB{……}

調(diào)用 methodA 時(shí),相當(dāng)于:

    public static void main(String[] args) {
        TransactionManager tm = null;
        try {
            tm = getTransactionManager();   //獲得一個(gè)JTA事務(wù)管理器
            tm.begin(); // 開(kāi)啟一個(gè)新的事務(wù)
            Transaction ts1 = tm.getTransaction();
            doSomeThing();
            tm.suspend();   //掛起當(dāng)前事務(wù)
            try {
                tm.begin();     // 重新開(kāi)啟第二個(gè)事務(wù)
                Transaction ts2 = tm.getTransaction();
                methodB();
                ts2.commit();   // 提交第二個(gè)事務(wù)
            } catch (RunTimeException ex) {
                ts2.rollback(); //回滾第二個(gè)事務(wù)
            } finally {
                //釋放資源
            }
            tm.resume(ts1);     // methodB執(zhí)行完后,復(fù)恢第一個(gè)事務(wù)
            doSomeThingB();
            ts1.commit();   // 提交第一個(gè)事務(wù)
        } catch (RunTimeException ex) {
            ts1.rollback();     // 回滾第一個(gè)事務(wù)
        } finally {
            //釋放資源
        }
    }

在這里,我把 ts1 稱為外層事務(wù),ts2 稱為內(nèi)層事務(wù)。從上面的代碼可以看出,ts2 與 ts1 是兩個(gè)獨(dú)立的事務(wù),互不相干。Ts2 是否成功并不依賴于 ts1。如果 methodA 方法在調(diào)用 methodB 方法后的 doSomeThingB 方法失敗了,而 methodB 方法所做的結(jié)果依然被提交。而除了 methodB 之外的其它代碼導(dǎo)致的結(jié)果卻被回滾了。使用 PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager 作為事務(wù)管理器。

PROPAGATION_NOT_SUPPORTED

總是非事務(wù)地執(zhí)行,并掛起任何存在的事務(wù)。使用 PROPAGATION_NOT_SUPPORTED,也需要使用 JtaTransactionManager 作為事務(wù)管理器。(代碼示例同上,可同理推出)

PROPAGATION_NEVER

總是非事務(wù)地執(zhí)行,如果存在一個(gè)活動(dòng)事務(wù),則拋出異常。

PROPAGATION_NESTED

如果一個(gè)活動(dòng)的事務(wù)存在,則運(yùn)行在一個(gè)嵌套的事務(wù)中。如果沒(méi)有活動(dòng)事務(wù),則按 TransactionDefinition.PROPAGATION_REQUIRED 屬性執(zhí)行。這是一個(gè)嵌套事務(wù),使用 JDBC 3.0 驅(qū)動(dòng)時(shí),僅僅支持 DataSourceTransactionManager 作為事務(wù)管理器。需要 JDBC 驅(qū)動(dòng)的 java.sql.Savepoint 類。有一些 JTA 的事務(wù)管理器實(shí)現(xiàn)可能也提供了同樣的功能。使用 PROPAGATION_NESTED,還需要把 PlatformTransactionManager 的 nestedTransactionAllowed 屬性設(shè)為 true,而 nestedTransactionAllowed 屬性值默認(rèn)為 false。

// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
    ……
    methodB();
    ……
}
// 事務(wù)屬性 PROPAGATION_NESTED
methodB{……}

如果單獨(dú)調(diào)用 methodB 方法,則按 REQUIRED 屬性執(zhí)行。如果調(diào)用methodA方法,相當(dāng)于下面的效果:

    public static void main(String[] args) {
        Connection con = null;
        Savepoint savepoint = null;
        try {
            con = getConnection();
            con.setAutoCommit(false);
            doSomeThingA();
            savepoint = con2.setSavepoint();
            try {
                methodB();
            } catch (RuntimeException ex) {
                con.rollback(savepoint);
            } finally {
                //釋放資源
            }
            doSomeThingB();
            con.commit();
        } catch (RuntimeException ex) {
            con.rollback();
        } finally {
            //釋放資源
        }
    }

當(dāng) methodB 方法調(diào)用之前,調(diào)用 setSavepoint 方法,保存當(dāng)前的狀態(tài)到 savepoint。如果 methodB 方法調(diào)用失敗,則恢復(fù)到之前保存的狀態(tài)。但是需要注意的是,這時(shí)的事務(wù)并沒(méi)有進(jìn)行提交,如果后續(xù)的代碼(doSomeThingB()方法)調(diào)用失敗,則回滾包括 methodB 方法的所有操作。

嵌套事務(wù)一個(gè)非常重要的概念就是內(nèi)層事務(wù)依賴于外層事務(wù)。外層事務(wù)失敗時(shí),會(huì)回滾內(nèi)層事務(wù)所做的動(dòng)作。而內(nèi)層事務(wù)操作失敗并不會(huì)引起外層事務(wù)的回滾。

PROPAGATION_NESTED 與 PROPAGATION_REQUIRES_NEW 的區(qū)別:它們非常類似,都像一個(gè)嵌套事務(wù),如果不存在一個(gè)活動(dòng)的事務(wù),都會(huì)開(kāi)啟一個(gè)新的事務(wù)。使用 PROPAGATION_REQUIRES_NEW 時(shí),內(nèi)層事務(wù)與外層事務(wù)就像兩個(gè)獨(dú)立的事務(wù)一樣,一旦內(nèi)層事務(wù)進(jìn)行了提交后,外層事務(wù)不能對(duì)其進(jìn)行回滾。兩個(gè)事務(wù)互不影響。兩個(gè)事務(wù)不是一個(gè)真正的嵌套事務(wù)。同時(shí)它需要 JTA 事務(wù)管理器的支持。

使用 PROPAGATION_NESTED 時(shí),外層事務(wù)的回滾可以引起內(nèi)層事務(wù)的回滾。而內(nèi)層事務(wù)的異常并不會(huì)導(dǎo)致外層事務(wù)的回滾,它是一個(gè)真正的嵌套事務(wù)。DataSourceTransactionManager 使用 savepoint 支持 PROPAGATION_NESTED 時(shí),需要 JDBC 3.0 以上驅(qū)動(dòng)及 1.4 以上的 JDK 版本支持。其它的 JTA TrasactionManager 實(shí)現(xiàn)可能有不同的支持方式。

PROPAGATION_REQUIRES_NEW 啟動(dòng)一個(gè)新的,不依賴于環(huán)境的“內(nèi)部”事務(wù)。這個(gè)事務(wù)將被完全 commited 或 rolled back 而不依賴于外部事務(wù),它擁有自己的隔離范圍,自己的鎖,等等。當(dāng)內(nèi)部事務(wù)開(kāi)始執(zhí)行時(shí),外部事務(wù)將被掛起,內(nèi)務(wù)事務(wù)結(jié)束時(shí),外部事務(wù)將繼續(xù)執(zhí)行。

另一方面,PROPAGATION_NESTED 開(kāi)始一個(gè)“嵌套的”事務(wù),它是已經(jīng)存在事務(wù)的一個(gè)真正的子事務(wù)。嵌套事務(wù)開(kāi)始執(zhí)行時(shí),它將取得一個(gè) savepoint。如果這個(gè)嵌套事務(wù)失敗,我們將回滾到此 savepoint。潛套事務(wù)是外部事務(wù)的一部分,只有外部事務(wù)結(jié)束后它才會(huì)被提交。

由此可見(jiàn),PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區(qū)別在于,PROPAGATION_REQUIRES_NEW 完全是一個(gè)新的事務(wù),而 PROPAGATION_NESTED 則是外部事務(wù)的子事務(wù),如果外部事務(wù) commit,嵌套事務(wù)也會(huì)被 commit,這個(gè)規(guī)則同樣適用于 roll back。
PROPAGATION_REQUIRED 應(yīng)該是我們首先的事務(wù)傳播行為。它能夠滿足我們大多數(shù)的事務(wù)需求。

spring 事務(wù)的四種實(shí)現(xiàn)方式

編程式事務(wù)

編程式事務(wù)管理對(duì)基于 POJO 的應(yīng)用來(lái)說(shuō)是唯一選擇。我們需要在代碼中調(diào)用 beginTransaction()、commit()、rollback() 等事務(wù)管理相關(guān)的方法,這就是編程式事務(wù)管理。基本上廢棄掉了。

基于 TransactionProxyFactoryBean 的聲明式事務(wù)管理
基于 @Transactional 的聲明式事務(wù)管理
基于 Aspectj AOP 配置事務(wù)

參考

Spring事務(wù)管理之幾種方式實(shí)現(xiàn)事務(wù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容