使用事務時應該避免的陷井

事務可實現“要麼完全成功,要不全部不成功”,保證數據的完整性和一致性,使我們在開發中能方便地實現一些業務邏輯。比如,在股票交易時,除了記錄交易的過程,還要更新交易完成之後的賬戶狀態。這兩個操作顯然必須“要麼完全成功,要麼全部不成功”,否則,你的麻煩就大了。

當然,如果你不關心數據的完整性和一致性的問題,那麼忘了事務吧,因爲引入鎖、數據庫併發等機制之後,對性能還是有影響的。

下面代碼中,placeTrade是一個完整的業務邏輯單元LUW(Logical units of work),實現記錄交易並更新賬戶的操作。

這裏使用了JPA,沒有使用事務。

上面的代碼沒有問題,但是在執行的時候,卻會發現insertTrade並沒有返回預期的交易編號,而是返回0,並且沒有任何異常。這就是這個文章提到的第一個陷井:ORM框架需要使用事務來同步對象緩存和數據庫。上面的代碼沒有使用事務,而且也沒有顯式地調用Flush,因此,在insert操作之後,數據並沒有被保存到數據庫中,因此,後面的更新操作也就不可能正確。

好,我們使用Spring來管理事務,通過Annotation使上面的代碼具有事務的能力。

在加上@TRansactional之後,你會發現,事務還是沒有被引入。這就是第二個陷井:使用Annotation引入事務,需要在Spring配置文件中添加<tx:annotation-driven transaction-manager="transactionManager"/>.  看到了吧,你還沒有告訴Spring用哪個transactionManager來管理事務,Spring怎麼會知道如何處理事務呢?你以爲Spring是傻瓜相機嗎?

但是僅僅使用@Transactional是遠遠不夠的,很多時候,即使你的代碼中加上了@Transactional,事務還是不起作用,原因就是你沒有指定事務參數。

默認的@Transactional如果沒有設定參數時,其propagation模式是REQUIRED, read-only標記是false。isolation level是READ_COMMITTED,這時,如果你的代碼中拋出異常,而且又被你通過try catch捕獲的話,事務是不回滾的。

好,我們知道了光用Annotation是不夠的,還需要加上一些參數,那麼,你已經很瞭解事務了嗎?我們再做下面的測試:

上面代碼中,我們設置read-only標記爲true,也就是說事務爲只讀,但是卻要進行插入操作。上面代碼執行結果如何呢?

答案是會正確執行。But why????

因爲這裏我們用了JDBC操作,沒有用ORM,而且propagation設爲SUPPORTS, 這樣Spring是不會創建一個事務的,而會將事務相關工作委託給數據庫的事務。而只有Spring開始了一個事務,read-only才起作用。這裏沒有事務,因此,read-only就被忽略了。

再看下面的代碼:

相同的代碼,我們把propagation設爲REQUIRED,那麼這時執行結果會怎麼樣呢? 答案是會拋出一個read only connection exception。爲什麼?自己想想吧。

如果對於只讀的一些查詢操作使用readonly標記會怎麼樣?考慮下面代碼。

問題是:getTrade的執行會在一個事務中嗎? 從字面上看,好像不會,因爲只有查詢操作,而且read-only。不幸地是,你錯了。你忘了propagation的默認設置值是REQUIRED,因此缺省狀態下,上面代碼也會啓動一個事務,執行完後COMMIT。不需要卻還是用上了事務,性能肯定受影響。所以,正確的方法是read-only的同時還要指定propagation=SUPPORTS,更好的方式是:查詢操作根本不需要使用事務。

關於Propagation.REQUIRES_NEW的陷井

有時候,開發人員搞不清楚REQUIRES_NEW和REQUIRED的區別,誤用了REQUIRES_NEW而造成很難發現的問題。比如下面代碼:

問題是:如果updateAcct發生異常,insertTrade操作會回滾嗎? 答案是:不會。Propagation.REQUIRES_NEW會掛起當前的事務,新建一個獨立的事務,執行完之後,提交併返回,激活先前掛起的事務繼續執行。此時發生異常回滾只能回滾第一個事務中的邏輯,而前面獨立事務已經COMMITTED了,抓瞎了吧。記錄了交易,卻沒有更新賬戶狀態,哈哈。解決辦法是:將REQUIRES_NEW改爲REQUIRED。這樣,如果方法發現自己已經處在一個事務中了,就不會重新啓動一個事務了。

關於Roll back的陷井

看下面的代碼:

怎麼樣,設置了使用事務,而且REQUIRED強調必須採用事務,這回應該沒問題了吧。

你又錯了。

在updateAcct時,如果發現該賬戶根本沒有足夠的錢來做這個交易,應該回滾,但是,不幸的是,回滾沒有發生。原因就在:你自己捕獲並處理了Exception. 懊惱吧,我自己受累多寫了代碼,還有錯了?

沒錯。這可能是使用事務方面最大的一個問題:運行時異常(未捕獲的異常)會自動強迫整個事務邏輯單元(LUW)回滾,但是被捕獲的異常則不會

怎麼樣,出力不討好了吧。趕緊把try catch去掉吧。

發佈了112 篇原創文章 · 獲贊 10 · 訪問量 52萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章