您好,登錄后才能下訂單哦!
這篇文章主要介紹“Spring的事務隔離級別與傳播性是什么意思”,在日常操作中,相信很多人在Spring的事務隔離級別與傳播性是什么意思問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Spring的事務隔離級別與傳播性是什么意思”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
這篇文章以一個問題開始,如果你知道答案的話就可以跳過不看啦@(o???)@
Q:在一個批量任務執行的過程中,調用多個子任務時,如果有一些子任務發生異常,只是回滾那些出現異常的任務,而不是整個批量任務,請問在Spring中事務需要如何配置才能實現這一功能呢?
隔離性(Isolation)作為事務特性的一個關鍵特性,它要求每個讀寫事務的對象對其他事務的操作對象能相互分離,即該事務提交前對其他事務都不可見,在數據庫層面都是使用鎖來實現。
事務的隔離級別從低到高有以下四種:
READ UNCOMMITTED(未提交讀):這是最低的隔離級別,其含義是允許一個事務讀取另外一個事務沒有提交的數據。READ UNCOMMITTED是一種危險的隔離級別,在實際開發中基本不會使用,主要是由于它會帶來臟讀問題。
時間 | 事務1 | 事務2 | 備注 |
---|---|---|---|
1 | 讀取庫存為100 | --- | --- |
2 | 扣減庫層50 | --- | 剩余50 |
3 | 扣減庫層50 | --- | |
4 | 提交事務 | 庫存保存為0 | |
5 | 回滾事務 | --- | 事務回滾為0 |
臟讀對于要求數據一致性的應用來說是致命的,目前主流的數據庫的隔離級別都不會設置成READ UNCOMMITTED。不過臟讀雖然看起來毫無用處,但是它主要優點是并發能力高,適合那些對數據一致性沒有要求而追求高并發的場景。
READ COMMITTED(讀寫提交): 它是指一個事務只能讀取另外一個事務已經提交的數據,不能讀取未提交的數據。READ COMMITTED會帶來不可重復讀的問題:
時間 | 事務1 | 事務2 | 備注 |
---|---|---|---|
1 | 讀取庫存為1 | ||
2 | 扣減庫存 | 事務未提交 | |
3 | 讀取庫存為1 | ||
4 | 提交事務 | 庫存變成0 | |
5 | 扣減庫存 | 庫存為0,無法扣減 |
不可重復讀和臟讀的區別是:臟讀讀取到的是未提交的數據,而不可重復讀讀到的確實已經提交的數據,但是違反了數據庫事務一致性的要求。
一般來說,不可重復讀的問題是可以接受的,因為其讀到的是已經提交的數據,本身并不會帶來很大的問題。因此,很多數據庫如(ORACLE,SQL SERVER)將其默認隔離級別設置為READ COMMITTED,允許不可重復讀的現象。
REPEATABLE READ (可重復讀):可重復讀的目標是為了克服READ COMMITED中出現的不可重復讀,它指在同一個事務內的查詢都是與事務開始時刻一致,以上表為例,在REPEATABLE READ隔離級別下它會發生如下變化:
時間 | 事務1 | 事務2 | 備注 |
---|---|---|---|
1 | 讀取庫存為1 | ||
2 | 扣減庫存 | 事務未提交 | |
3 | 讀取庫存 | 不允許讀取,等待事務1提交 | |
4 | 提交事務 | 庫存變成0 | |
5 | 讀取庫存 | 庫存為0,無法扣減 |
REPEATABLE READ雖然解決了不可重復讀問題,但是他又會帶來幻讀問題,幻讀是指,在一個事務中,第一次查詢某條記錄,發現沒有,但是,當試圖更新這條不存在的記錄時,竟然能成功,并且,再次讀取同一條記錄,它就神奇地出現了。
時間 | 事務A | 事務2 | 備注 |
---|---|---|---|
1 | begin | begin | |
2 | 讀取id為100的數據 | 沒有數據 | |
3 | 插入id為100的數據 | ||
4 | 提交事務 | ||
5 | 讀取id為100的數據 | 沒有數據 | |
6 | 更新id為100的數據 | 成功 | |
7 | 讀取id為100的數據 | 讀取成功 | |
8 | 提交事務 |
事務B在第2步第一次讀取id=99
的記錄時,讀到的記錄為空,說明不存在id=99
的記錄。隨后,事務A在第3步插入了一條id=99
的記錄并提交。事務B在第5步再次讀取id=99
的記錄時,讀到的記錄仍然為空,但是,事務B在第6步試圖更新這條不存在的記錄時,竟然成功了,并且,事務B在第8步再次讀取id=99
的記錄時,記錄出現了。
SERIALIZABLE(串行化):數據庫最高的隔離級別,它要求所有的SQL都會按照順序執行,這樣可以克服上述所有隔離出現的各種問題,能夠完全包住數據的一致性。
在Spring項目中配置隔離級別只需要做如下操作
@Transactional(isolation = Isolation.SERIALIZABLE) public int insertUser(User user){ return userDao.insertUser(user); }
上面的代碼中我們使用了串行化的隔離級別來包住數據的一致性,這使它將阻塞其他的事務進行并發,所以它只能運用在那些低并發而又需要保證數據一致性的場景下。
隔離級別字典:
DEFAULT(-1), ## 數據庫默認級別 READ_UNCOMMITTED(1), READ_COMMITTED(2), REPEATABLE_READ(4), SERIALIZABLE(8);
在Spring中,當一個方法調用另外一個方法時,可以讓事務采取不同的策略工作,如新建事務或者掛起當前事務等,這便是事務的傳播行為。
在Spring的事務機制中對數據庫存在7種傳播行為,通過枚舉類Propagation
定義。
public enum Propagation { /** * 需要事務,默認傳播性行為。 * 如果當前存在事務,就沿用當前事務,否則新建一個事務運行子方法 */ REQUIRED(0), /** * 支持事務,如果當前存在事務,就沿用當前事務, * 如果不存在,則繼續采用無事務的方式運行子方法 */ SUPPORTS(1), /** * 必須使用事務,如果當前沒有事務,拋出異常 * 如果存在當前事務,就沿用當前事務 */ MANDATORY(2), /** * 無論當前事務是否存在,都會創建新事務允許方法 * 這樣新事務就可以擁有新的鎖和隔離級別等特性,與當前事務相互獨立 */ REQUIRES_NEW(3), /** * 不支持事務,當前存在事務時,將掛起事務,運行方法 */ NOT_SUPPORTED(4), /** * 不支持事務,如果當前方法存在事務,將拋出異常,否則繼續使用無事務機制運行 */ NEVER(5), /** * 在當前方法調用子方法時,如果子方法發生異常 * 只回滾子方法執行過的SQL,而不回滾當前方法的事務 */ NESTED(6); ...... }
日常開發中基本只會使用到REQUIRED(0)
,REQUIRES_NEW(3)
,NESTED(6)
三種。
NESTED
和REQUIRES_NEW
是有區別的。NESTED
傳播行為會沿用當前事務的隔離級別和鎖等特性,而REQUIRES_NEW
則可以擁有自己獨立的隔離級別和鎖等特性。
NESTED
的實現主要依賴于數據庫的保存點(SAVEPOINT)技術,SAVEPOINT記錄了一個保存點,可以通過ROLLBACK TO SAVEPOINT
來回滾到某個保存點。如果數據庫支持保存點技術時就啟用保存點技術;如果不支持就會新建一個事務去執行代碼,也就相當于REQUIRES_NEW
。
如果一個類中自身方法的調用,我們稱之為自調用。如一個訂單業務實現類OrderServiceImpl中有methodA方法調用了自身類的methodB方法就是自調用,如:
@Transactional public void methodA(){ for (int i = 0; i < 10; i++) { methodB(); } } @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW) public int methodB(){ ...... }
在上面方法中不管methodB如何設置隔離級別和傳播行為都是不生效的。即自調用失效。
這主要是由于@Transactional的底層實現原理是基于AOP實現,而AOP的原理是動態代理,在自調用的過程中是類自身的調用,而不是代理對象去調用,那么就不會產生AOP,于是就發生了自調用失敗的現象。
要克服這個問題,有2種方法:
編寫兩個Service,用一個Service的methodA去調用另外一個Service的methodB方法,這樣就是代理對象的調用,不會有問題;
在同一個Service中,methodA不直接調用methodB,而是先從Spring IOC容器中重新獲取代理對象`OrderServiceImpl·,獲取到后再去調用methodB。說起來有點亂,還是show you the code。
public class OrderServiceImpl implements OrderService,ApplicationContextAware { private ApplicationContext applicationContext = null; @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Transactional public void methodA(){ OrderService orderService = applicationContext.getBean(OrderService.class); for (int i = 0; i < 10; i++) { orderService.methodB(); } } @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW) public int methodB(){ ...... } }
上面代碼中我們先實現了ApplicationContextAware
接口,然后通過applicationContext.getBean()
獲取了OrderService
的接口對象。這個時候獲取到的是一個代理對象,也就能正常使用AOP的動態代理了。
回到最開始的那個問題,看完這篇文章是不是有答案了呢?
到此,關于“Spring的事務隔離級別與傳播性是什么意思”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。