溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

MySQL中本地事務(wù)的示例分析

發(fā)布時間:2021-11-17 09:53:00 來源:億速云 閱讀:120 作者:小新 欄目:大數(shù)據(jù)

小編給大家分享一下MySQL中本地事務(wù)的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

1. MySQL中的本地事務(wù)

1.1 什么是事務(wù)

事務(wù)是邏輯上的一組操作,要么都執(zhí)行,要么都不執(zhí)行,一榮俱榮,一損俱損。數(shù)據(jù)庫事務(wù)有嚴(yán)格的定義,必須滿足4個特性。

原子性:事務(wù)是最小的執(zhí)行單位,不允許分割。事務(wù)的原子性確保動作要么全部完成,要么完全不起作用;
一致性:執(zhí)行事務(wù)前后,數(shù)據(jù)保持一致,多個事務(wù)對同一個數(shù)據(jù)讀取的結(jié)果是相同的,事務(wù)操作成功前后,數(shù)據(jù)庫所處的狀態(tài)和它的業(yè)務(wù)規(guī)則是一致的。在這些事務(wù)特性中,數(shù)據(jù)一致性是最終目標(biāo),其它特性都是為達(dá)到這個目標(biāo)采取的措施、要求或者手段。

ACID中的一致性和CAP中的一致性有什么區(qū)別?

二者完全不是一個事情,數(shù)據(jù)庫對于 ACID 中的一致性的定義是這樣的:如果一個事務(wù)原子地在一個一致地數(shù)據(jù)庫中獨立運行,那么在它執(zhí)行之后,數(shù)據(jù)庫的狀態(tài)一定是一致的。對于這個概念,它的第一層意思就是對于數(shù)據(jù)完整性的約束,包括主鍵約束、引用約束以及一些約束檢查等等,在事務(wù)的執(zhí)行的前后以及過程中不會違背對數(shù)據(jù)完整性的約束,所有對數(shù)據(jù)庫寫入的操作都應(yīng)該是合法的,并不能產(chǎn)生不合法的數(shù)據(jù)狀態(tài)。而第二層意思其實是指邏輯上的對于開發(fā)者的要求,我們要在代碼中寫出正確的事務(wù)邏輯,比如銀行轉(zhuǎn)賬,事務(wù)中的邏輯不可能只扣錢或者只加錢,這是應(yīng)用層面上對于數(shù)據(jù)庫一致性的要求。即,數(shù)據(jù)庫 ACID 中的一致性對事務(wù)的要求不止包含對數(shù)據(jù)完整性以及合法性的檢查,還包含應(yīng)用層面邏輯的正確。

CAP 定理中的數(shù)據(jù)一致性,其實是說分布式系統(tǒng)中的各個節(jié)點中對于同一數(shù)據(jù)的拷貝有著相同的值。

隔離性:并發(fā)訪問數(shù)據(jù)庫時,一個用戶的事務(wù)不被其他事務(wù)所干擾,各并發(fā)事務(wù)之間數(shù)據(jù)庫是獨立的,采用數(shù)據(jù)庫鎖機制來保證事務(wù)的隔離性;
持久性:一個事務(wù)被提交之后。它對數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的,即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對其有任何影響。

1.2 事務(wù)隔離級別

在典型的應(yīng)用程序中,多個事務(wù)并發(fā)運行,經(jīng)常會操作相同的數(shù)據(jù)來完成各自的任務(wù)(多個用戶對統(tǒng)一數(shù)據(jù)進行操作)。并發(fā)雖然是必須的,但可能會導(dǎo)致以下的問題:
臟讀(Dirtyread):當(dāng)一個事務(wù)正在訪問數(shù)據(jù)并且對數(shù)據(jù)進行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中,這時另外一個事務(wù)也訪問了這個數(shù)據(jù),然后使用了這個數(shù)據(jù)。因為這個數(shù)據(jù)是還沒有提交的數(shù)據(jù),那么另外一個事務(wù)讀到的這個數(shù)據(jù)是“臟數(shù)據(jù)”,依據(jù)“臟數(shù)據(jù)”所做的操作可能是不正確的。
丟失修改(Losttomodify):指在一個事務(wù)讀取一個數(shù)據(jù)時,另外一個事務(wù)也訪問了該數(shù)據(jù),那么在第一個事務(wù)中修改了這個數(shù)據(jù)后,第二個事務(wù)也修改了這個數(shù)據(jù)。這樣第一個事務(wù)內(nèi)的修改結(jié)果就被丟失,因此稱為丟失修改。例如:事務(wù)1讀取某表中的數(shù)據(jù)A=20,事務(wù)2也讀取A=20,事務(wù)1修改A=A-1,事務(wù)2也修改A=A-1,最終結(jié)果A=19,事務(wù)1的修改被丟失。
不可重復(fù)讀(Unrepeatableread):指在一個事務(wù)內(nèi)多次讀同一數(shù)據(jù)。在這個事務(wù)還沒有結(jié)束時,另一個事務(wù)也訪問該數(shù)據(jù)。那么,在第一個事務(wù)中的兩次讀數(shù)據(jù)之間,由于第二個事務(wù)的修改導(dǎo)致第一個事務(wù)兩次讀取的數(shù)據(jù)可能不太一樣。這就發(fā)生了在一個事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的情況,因此稱為不可重復(fù)讀。
幻讀(Phantomread):幻讀與不可重復(fù)讀類似。它發(fā)生在一個事務(wù)(T1)讀取了幾行數(shù)據(jù),接著另一個并發(fā)事務(wù)(T2)插入了一些數(shù)據(jù)時。在隨后的查詢中,第一個事務(wù)(T1)就會發(fā)現(xiàn)多了一些原本不存在的記錄,就好像發(fā)生了幻覺一樣,所以稱為幻讀。

不可重復(fù)度和幻讀區(qū)別:不可重復(fù)讀的重點是修改,幻讀的重點在于新增或者刪除。例1(同樣的條件,你讀取過的數(shù)據(jù),再次讀取出來發(fā)現(xiàn)值不一樣了):事務(wù)1中的A先生讀取自己的工資為1000的操作還沒完成,事務(wù)2中的B先生就修改了A的工資為2000,導(dǎo)致A再讀自己的工資時工資變?yōu)?000;這就是不可重復(fù)讀。
例2(同樣的條件,第1次和第2次讀出來的記錄數(shù)不一樣):假某工資單表中工資大于3000的有4人,事務(wù)1讀取了所有工資大于3000的人,共查到4條記錄,這時事務(wù)2又插入了一條工資大于3000的記錄,事務(wù)1再次讀取時查到的記錄就變?yōu)榱?條,這樣就導(dǎo)致了幻讀。

SQL標(biāo)準(zhǔn)定義了四個隔離級別隔離性和一致性其實是一個需要開發(fā)者去權(quán)衡的問題,為數(shù)據(jù)庫提供什么樣的隔離性層級也就決定了數(shù)據(jù)庫的性能以及可以達(dá)到什么樣的一致性。
READ-UNCOMMITTED(讀取未提交):最低的隔離級別,允許讀取尚未提交的數(shù)據(jù)變更,可能會導(dǎo)致臟讀、幻讀或不可重復(fù)讀。
READ-COMMITTED(讀取已提交):允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù),可以阻止臟讀,但是幻讀或不可重復(fù)讀仍有可能發(fā)生。(MySQL可重復(fù)讀隔離級別的實現(xiàn)原理)
REPEATABLE-READ(可重復(fù)讀):對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改,可以阻止臟讀和不可重復(fù)讀,但幻讀仍有可能發(fā)生。
SERIALIZABLE(可串行化):最高的隔離級別,完全服從ACID的隔離級別。所有的事務(wù)依次逐個執(zhí)行,這樣事務(wù)之間就完全不可能產(chǎn)生干擾,也就是說,該級別可以防止臟讀、不可重復(fù)讀以及幻讀。

隔離級別臟讀不可重復(fù)讀幻影讀
READ-UNCOMMITTED
READ-COMMITTED×
REPEATABLE-READ××
SERIALIZABLE×××

     表-數(shù)據(jù)并發(fā)問題在不同隔離級別的出現(xiàn)

這里需要注意的是:與SQL標(biāo)準(zhǔn)不同的地方在于InnoDB存儲引擎在REPEATABLE-READ(可重讀)事務(wù)隔離級別下使用的是Next-KeyLock鎖算法,因此可以避免幻讀的產(chǎn)生,這與其他數(shù)據(jù)庫系統(tǒng)(如SQLServer)是不同的。所以說InnoDB存儲引擎的默認(rèn)支持的隔離級別是REPEATABLE-READ(可重讀)已經(jīng)可以完全保證事務(wù)的隔離性要求,即達(dá)到了SQL標(biāo)準(zhǔn)的SERIALIZABLE(可串行化)隔離級別。因為隔離級別越低,事務(wù)請求的鎖越少,所以大部分?jǐn)?shù)據(jù)庫系統(tǒng)的隔離級別都是READ-COMMITTED(讀取提交內(nèi)容):,但是你要知道的是InnoDB存儲引擎默認(rèn)使用REPEATABLE-READ(可重讀)并不會有任何性能損失。
InnoDB存儲引擎在分布式事務(wù)的情況下一般會用到SERIALIZABLE(可串行化)隔離級別。

1.3 MySQL事務(wù)基本實現(xiàn)

事務(wù)的原子性和持久性是由事務(wù)日志(transaction log)保證的,回滾日志用于對事務(wù)的影響進行撤銷,重做日志在錯誤處理時對已經(jīng)提交的事務(wù)進行重做。它們能保證兩點:

  • 發(fā)生錯誤或者需要回滾的事務(wù)能夠成功回滾(原子性);

  • 在事務(wù)提交后,數(shù)據(jù)沒來得及寫會磁盤就宕機時,在下次重新啟動后能夠成功恢復(fù)數(shù)據(jù)(持久性);

UndoLog的原理很簡單,為了滿足事務(wù)的原子性,在操作任何數(shù)據(jù)之前,首先將數(shù)據(jù)備份到一個地方(這個存儲數(shù)據(jù)備份的地方稱為UndoLog)。然后進行數(shù)據(jù)的修改。如果出現(xiàn)了錯誤或者用戶執(zhí)行了ROLLBACK語句,系統(tǒng)可以利用Undo Log中的備份將數(shù)據(jù)恢復(fù)到事務(wù)開始之前的狀態(tài),UndoLog并不能將數(shù)據(jù)庫物理地恢復(fù)到執(zhí)行語句或者事務(wù)之前的樣子;它是邏輯日志,當(dāng)回滾日志被使用時,它只會按照日志邏輯地將數(shù)據(jù)庫中的修改撤銷掉看,可以理解為我們在事務(wù)中使用的每一條INSERT都對應(yīng)了一條DELETE,每一條UPDATE也都對應(yīng)一條相反的UPDATE語句。

RedoLog記錄的是新數(shù)據(jù)的備份, 保障的是事務(wù)的持久性和一致性。在事務(wù)提交前,只要將RedoLog持久化即可,不需要將數(shù)據(jù)持久化。當(dāng)系統(tǒng)崩潰時,雖然數(shù)據(jù)沒有持久化,但是RedoLog已經(jīng)持久化。系統(tǒng)可以根據(jù)RedoLog的內(nèi)容,將所有數(shù)據(jù)恢復(fù)到最新的狀態(tài)。 (深入分析可以參考面向信仰編程-淺入深出MySQL中事務(wù)的實現(xiàn)-回滾日志、重做日志)
redo log和undo log的過程分析
eg : 假設(shè)有2個數(shù)值,分別為A和B,值為1,2
1 start transaction;
2 記錄 A=1 到undo log;
3 update A = 3;
4 記錄 A=3 到redo log;
5 記錄 B=2 到undo log;
6 update B = 4;
7 記錄B = 4 到redo log;
8 將redo log刷新到磁盤
9 commit
在1-8的任意一步系統(tǒng)宕機,事務(wù)未提交,該事務(wù)就不會對磁盤上的數(shù)據(jù)做任何影響.
如果在8-9之間宕機,恢復(fù)之后可以選擇回滾,也可以選擇繼續(xù)完成事務(wù)提交,因為此時redo log已經(jīng)持久化
若在9之后系統(tǒng)宕機,內(nèi)存映射中變更的數(shù)據(jù)還來不及刷回磁盤,那么系統(tǒng)恢復(fù)之后,可以根據(jù)redo log把數(shù)據(jù)刷回磁盤

數(shù)據(jù)庫對于隔離級別的實現(xiàn)就是使用并發(fā)控制機制對在同一時間執(zhí)行的事務(wù)進行控制,限制不同的事務(wù)對于同一資源的訪問和更新,而最重要也最常見的并發(fā)控制機制,主要有鎖、時間戳(即樂觀鎖,并不是真正的鎖機制,而是一種思想)、多版本MVCC

2. Spring的事務(wù)管理

2.1 Spring對事務(wù)管理的支持

Spring為事務(wù)管理提供了一致的編程模板,在高層建立了統(tǒng)一的事務(wù)抽象。也就是說,不管是選擇Spring JDBC、Hibernate、JPA還是選擇MyBatis,Spring都可以讓用戶使用統(tǒng)一的編程模型進行事務(wù)管理。

Spring為事務(wù)管理提供了一致的編程模板,在高層次建立了統(tǒng)一的事務(wù)抽象。像Spring DAO為不同的持久化技術(shù)實現(xiàn)提供模板類一樣,Spring事務(wù)管理繼承了這一風(fēng)格,也提供了事務(wù)模板類TransactionTemplate。通過TransactionTemplate并配合使用事務(wù)回調(diào)TransactionCallback指定具體的持久化操作就可以通過編程方式實現(xiàn)事務(wù)管理,而無須關(guān)注資源獲取、復(fù)用、釋放、事務(wù)同步和異常處理的操作。

2.1.1 事務(wù)管理關(guān)鍵抽象

在Spring事務(wù)管理SPI的抽象層主要包括3個接口,分別是PlatformTransactionManager、TransactionDefinition和TransactionStatus,它們位于org.springframework.transaction包中。3者關(guān)系如圖:

                                        MySQL中本地事務(wù)的示例分析

                                                        圖-Spring事務(wù)管理SPI抽象

其中,TransactionDefinition用于描述事務(wù)的隔離級別,超時時間、是否為只讀事務(wù)和事務(wù)傳播規(guī)則等控制事務(wù)具體行為的事務(wù)屬性。這些事務(wù)屬性可以通過XML配置、注解描述或手工編程的方式設(shè)置。PlatformTransactionManager根據(jù)TransactionDefinition提供的事務(wù)屬性配置信息創(chuàng)建事務(wù),并用TransactionStatus描述這個激活事務(wù)的狀態(tài)。

  • TransactionDefinition

事務(wù)隔離:TransactionDefinition使用了java.sql.Connection接口中同名的4個隔離級別,此外,TransactionDefinition還定義了一個默認(rèn)的隔離級別,它表示使用底層數(shù)據(jù)庫的默認(rèn)隔離級別。
事務(wù)傳播:通常在一個事務(wù)中執(zhí)行的所有代碼都會同一事務(wù)的上下文中。但是Spring也提供了幾個可選的事務(wù)傳播類型,例如簡單地參與到現(xiàn)有的事務(wù)中,或者掛起當(dāng)前的事務(wù),創(chuàng)建一個新事務(wù)。
事務(wù)超時:事務(wù)在超時前能運行多久,超過時間后,事務(wù)被回滾。有些事務(wù)管理器不支持事務(wù)過期的功能,這時如果設(shè)置TIMEOUT_DEFAULT等值時將拋出異常。
只讀狀態(tài):只讀事務(wù)不修改任何數(shù)據(jù),主要用于優(yōu)化,如果更改數(shù)據(jù)就會拋出異常。

spring允許通過XML或者注解元數(shù)據(jù)的方式為一個有事務(wù)要求的服務(wù)類方法配置事務(wù)屬性,這些信息作為Spring事務(wù)管理框架的輸入,Spring將自動按照事務(wù)屬性信息的指示,為目標(biāo)方法提供相應(yīng)的事務(wù)支持。

  • TransactionStatus

TransactionStatus代表一個事務(wù)的具體運行狀態(tài),事務(wù)管理器通過該接口獲取事務(wù)的運行期狀態(tài)信息,也可以通過該接口間接地回滾事務(wù),它相比于在拋出異常時回滾事務(wù)的方式更具有可控性。

  • PlatformTransactionManager

PlatformTransactionManager是事務(wù)的最高層抽象,它提供了3個接口方法:
TransactionStatus getTransaction(TransactionDefinition definition):該方法根據(jù)事務(wù)定義信息從事務(wù)環(huán)境中返回一個已存在的事務(wù),或者創(chuàng)建一個新的事務(wù),并用TransactionStatus描述這個事務(wù)的狀態(tài)。
commit(TransactionStatus status):根據(jù)事務(wù)的狀態(tài)提交事務(wù),如果事務(wù)狀態(tài)已經(jīng)被標(biāo)識為rollback-only,該方法將執(zhí)行一個回滾事務(wù)的操作。
rollback(TransactionStatus status):回滾事務(wù),當(dāng)提交事務(wù)拋出異常時,回滾會被隱式執(zhí)行。

2.1.2 Spring的事務(wù)管理器實現(xiàn)類

Spring將事務(wù)管理委托給底層具體的持久化實現(xiàn)框架完成,因此Spring為不同的持久化框架提供了PlatformTransactionManager接口的實現(xiàn)類,如下圖:

                              MySQL中本地事務(wù)的示例分析                 

                                             圖-不同持久化技術(shù)對應(yīng)的事務(wù)管理器實現(xiàn)類 

這些事務(wù)管理器都是對特定事務(wù)實現(xiàn)框架的代理,這樣我們就可以通過spring的高級抽象,對不同種類的事務(wù)實現(xiàn)使用相同的方式進行管理,而不用關(guān)心具體的實現(xiàn)。要實現(xiàn)事務(wù)管理,首先要在Spring中配置好相應(yīng)的事務(wù)管理器,為事務(wù)管理器指定數(shù)據(jù)資源及一些其它事務(wù)管理控制屬性。

2.1.3 事務(wù)同步管理器

Spring將JDBC的Connection、Hibernate的Session等訪問數(shù)據(jù)庫的連接或會話對象統(tǒng)稱為資源。這些資源在同一時刻是不能多線程共享的,為了讓DAO、Service能做到singleton,Spring的事務(wù)同步管理器類org.springframework.transaction.support.TransactionSynchronizationManager使用ThreadLocal為不同事務(wù)線程提供了獨立的資源副本,同時維護事務(wù)配置的屬性和運行狀態(tài)信息。

2.1.4 事務(wù)傳播行為

在一個service接口中可能會調(diào)用另一個service接口的方法,以共同完成一個完整的業(yè)務(wù)操作,Spring通過事務(wù)傳播行為控制當(dāng)前的事務(wù)如何傳播到被嵌套調(diào)用的目標(biāo)服務(wù)接口方法中。Spring在TransactionDefinition接口中規(guī)定了7種類型的事務(wù)傳播行為,如下圖

                          MySQL中本地事務(wù)的示例分析

                                                       圖-事務(wù)傳播行為類型 

2.2 編程式的事務(wù)管理

Spring為編程式事務(wù)管理提供了模板類org.springframework.transaction.support.TransactionTemplate,和那些持久化模板類一樣,TransactionTemplate也是線程安全的。TransactionTemplate有2個重要的方法:

// 設(shè)置事務(wù)管理器
void  setTransactionManager(PlatformTransactionManager  transactionManager)
// 在TransactionCallback回調(diào)接口中定義需要以事務(wù)的方式組織的數(shù)據(jù)訪問邏輯
Object  execute(TransactionCallback  action)
TransactionCallback接口只有一個方法:Object  doInTransaction(TransactionStatus  status)。如果操作不會返回結(jié)果,可以使用TransactionCallback的子接口TransactionCallbackWithoutResult。

2.3 使用注解配置聲明式事務(wù)

Spring Boot中使用@Transactional注解配置事務(wù)管理

2.4 Spring事務(wù)管理難點與坑

2.4.1 DAO和事務(wù)管理的牽絆

是否用了Spring,就一定要用Spring事務(wù)管理器,否則就無法進行數(shù)據(jù)的持久化操作呢?答案是否定的,脫離了事務(wù)性,DAO照樣可以順利地進行數(shù)據(jù)操作。對于強調(diào)速度的應(yīng)用,數(shù)據(jù)庫本身可能就不支持事務(wù),如MyISAM引擎的數(shù)據(jù)庫,這是無需配置事務(wù)管理器,即使配置了,也是沒有實際用處的。

2.4.2 應(yīng)用分層迷惑

將面向接口編程奉為圭臬,過分強制面向接口編程除了會帶來更多的的類文件,并不會有什么好處。Spring事務(wù)管理支持在Controller層直接添加事務(wù)注解并生效,因此事務(wù)管理并不一定強制應(yīng)用必須嚴(yán)格分層,可以根據(jù)實際應(yīng)用出發(fā),根據(jù)實際需要進行編程。

2.4.3 事務(wù)方法嵌套調(diào)用的迷惑

除了事務(wù)的傳播行為,對于事務(wù)的其它特性,Spring是借助底層資源的功能來完成的,無非充當(dāng)了一個代理的角色。但是Spring事務(wù)的傳播行為卻是Spring憑借自身的框架提供的功能。

  • 不同服務(wù)方法之間的嵌套調(diào)用

例如對于調(diào)用鏈Service1#method1()->Service2#method2()->Service3#method3(),那么這三個方法通過Spring的事務(wù)傳播機制都可以工作在同一個事務(wù)中。

  • 同一個服務(wù)不同方法的嵌套調(diào)用(自我調(diào)用)

首先調(diào)用的是AOP代理對象而不是目標(biāo)對象,首先執(zhí)行事務(wù)切面,事務(wù)切面內(nèi)部通過TransactionInterceptor環(huán)繞增強進行事務(wù)的增強,即進入目標(biāo)方法之前開啟事務(wù),退出目標(biāo)方法時提交/回滾事務(wù)。目標(biāo)對象內(nèi)部的自我調(diào)用將無法實施切面中的增強。

public interface AService {  
    public void a();  
    public void b();  
}  
   
@Service()  
public class AServiceImpl1 implements AService{  
    @Transactional(propagation = Propagation.REQUIRED)  
    public void a() {
// 此處的this指向目標(biāo)對象,因此調(diào)用this.b()將不會執(zhí)行b事務(wù)切面,即不會執(zhí)行事務(wù)增強(通過日志可以觀察到)  
        this.b();  
    }  
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void b() {  
    }  
}

如何解決?

方法一:調(diào)用AOP代理對象的b方法即可執(zhí)行事務(wù)切面進行事務(wù)增強

需要開啟暴露Aop代理到ThreadLocal支持,并將this.b()修改為((AService) AopContext.currentProxy()).b();

這種通過ThreadLocal暴露Aop代理對象適合解決所有場景(不管是singleton Bean還是prototype Bean)的AOP代理獲取問題(即能解決目標(biāo)對象的自我調(diào)用問題);

方法二:通過初始化方法在目標(biāo)對象中注入代理對象

這種方式不是很靈活,所有需要自我調(diào)用的實現(xiàn)類必須重復(fù)實現(xiàn)代碼

@Service  
public class AServiceImpl3 implements AService{  
    @Autowired  //①  注入上下文  
    private ApplicationContext context;  
      
    private AService proxySelf; //②  表示代理對象,不是目標(biāo)對象  
    @PostConstruct  //③ 初始化方法  
    private void setSelf() {  
        //從上下文獲取代理對象(如果通過proxtSelf=this是不對的,this是目標(biāo)對象)  
        //此種方法不適合于prototype Bean,因為每次getBean返回一個新的Bean  
        proxySelf = context.getBean(AService.class);   
    }  
    @Transactional(propagation = Propagation.REQUIRED)  
    public void a() {  
       proxySelf.b(); //④ 調(diào)用代理對象的方法 這樣可以執(zhí)行事務(wù)切面  
    }  
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void b() {  
    }  
}

方法三:通過BeanPostProcessor在目標(biāo)對象中注入代理對象

需要注意到循環(huán)依賴和非singleton bean的影響,以下方式能解決singleton之間的循環(huán)依賴問題,但是不能解決循環(huán)依賴中包含prototype Bean的自我調(diào)用問題。

// 即我們自定義的BeanPostProcessor (InjectBeanSelfProcessor)
// 如果發(fā)現(xiàn)我們的Bean是實現(xiàn)了該標(biāo)識接口就調(diào)用setSelf注入代理對象。
public interface BeanSelfAware {  
    void setSelf(Object proxyBean);  
} 

@Component  
public class InjectBeanSelfProcessor implements BeanPostProcessor, ApplicationContextAware {  
    private ApplicationContext context;  
    //① 注入ApplicationContext  
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {  
        this.context = applicationContext;  
    }  
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {  
        if(!(bean instanceof BeanSelfAware)) { //② 如果Bean沒有實現(xiàn)BeanSelfAware標(biāo)識接口 跳過  
            return bean;  
        }  
        if(AopUtils.isAopProxy(bean)) { //③ 如果當(dāng)前對象是AOP代理對象,直接注入  
            ((BeanSelfAware) bean).setSelf(bean);  
        } else {  
            //④ 如果當(dāng)前對象不是AOP代理,則通過context.getBean(beanName)獲取代理對象并注入  
            //此種方式不適合解決prototype Bean的代理對象注入  
            ((BeanSelfAware)bean).setSelf(context.getBean(beanName));  
        }  
        return bean;  
    }  
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
        return bean;  
    }  
}

擴展:靈活使用事務(wù)注解并非一定需要接口

如果在一個業(yè)務(wù)方法中包含查詢、修改等邏輯,但其實只有這些修改操作需要實施業(yè)務(wù)增強,這是應(yīng)該怎么處理?可以讓改service實現(xiàn)BeanSelfAware接口,然后在service注入代理對象,通過代理對象

@Service  
public class AServiceImpl1 implements AService,BeanSelfAware{

    private AServiceImpl1 self;

    @Override
    public void setSelf(AServiceImpl1 proxy) {
        this.self = proxy;
    }

    @Override  
    public void a() {
       // ... 其它查詢等無需事務(wù)邏輯
       // 需要事務(wù)增強的修改邏輯
       self.m();
  
    } 
        
    @Transactional  
    public void m() {  
    }  
}

2.4.4 多線程的困惑

簡書-Spring中的Bean是線程安全的嗎?

在相同線程中進行相互的嵌套調(diào)用的事務(wù)方法工作在相同的事務(wù)中,如果這些相互嵌套調(diào)用的方法工作在不同線程中,則不同的線程下的事務(wù)方法工作在獨立的事務(wù)中。

2.4.5 聯(lián)合軍種作戰(zhàn)的混亂

如果用戶采用了一種高端的ORM技術(shù)(Hibernate、JPA、JDO),同時還采用了一種JDBC技術(shù)(Spring JDBC、Mybatis),由于前者的會話Session是對后者連接Connection的封裝,Spring會足夠智能地在同一個事務(wù)線程中讓前者的會話封裝后者的連接,所以只要只要直接采用前者前者的事務(wù)管理器就可以了。

                                MySQL中本地事務(wù)的示例分析

                                                   圖-混合數(shù)據(jù)訪問技術(shù)所對應(yīng)的事務(wù)管理器

2.4.6 特殊方法成漏網(wǎng)之魚

由于Spring事務(wù)管理是基于接口代理或者動態(tài)字節(jié)碼技術(shù),通過AOP實施事務(wù)增強的,雖然Spring依然支持AspectJ在類的加載期間實施增強,但這種方法很少使用,這里不做討論。

對于基于接口動態(tài)代理的AOP事務(wù)增強來說,由于接口的方法都必須是public的,這就要求實現(xiàn)類的實現(xiàn)方法也必須是public的(不能是protected、private)的,同時不能使用static修飾符。所以,可以實施接口動態(tài)代理的方法只能是public或public  final修飾符的方法,其它方法都不能被動態(tài)代理,相應(yīng)地也就不能實施AOP增強,換句話說即不能進行Spring事務(wù)的增強。

基于CGLib字節(jié)碼動態(tài)代理的方案是通過擴展被增強類,動態(tài)創(chuàng)建起子類的方式進行AOP增強植入的,由于使用final、static、private修飾符的方法都不能被子類覆蓋,相應(yīng)地這些方法無法實施AOP增強。所以方法簽名必須特別注意這些修飾符的使用,以免成為事務(wù)管理的漏網(wǎng)之魚。

需要注意的是,我們說這些方法不能被Spring進行AOP事務(wù)增強,是指這些方法不能啟動事務(wù),但是外層方法的事務(wù)上下文依舊可以順利地傳播到這些方法中。這些不能被事務(wù)增強的方法和可被事務(wù)增強的方法的唯一區(qū)別在于“是否可以主動開啟一個新事務(wù)”,前者可以而后者不可以,對于事務(wù)傳播行為來說,二者是完全相同的。

                               MySQL中本地事務(wù)的示例分析

                                                                圖-Spring事務(wù)管理漏網(wǎng)之魚

以上是“MySQL中本地事務(wù)的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI