日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

事务策略: 了解事务陷阱--转

發布時間:2025/4/5 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 事务策略: 了解事务陷阱--转 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在 Java 平臺中實現事務時要注意的常見錯誤

在應用程序中使用事務常常是為了維護高度的數據完整性和一致性。如果不關心數據的質量,就不必使用事務。畢竟,Java 平臺中的事務支持會降低性能,引發鎖定問題和數據庫并發性問題,而且會增加應用程序的復雜性。

關于本系列

事務提高了數據的質量、完整性和一致性,使應用程序更健壯。在 Java 應用程序中實現成功的事務處理不是一件容易的事,設計和編碼幾乎一樣重要。在這份新的?系列文章?中,Mark Richards 將帶領您設計一個有效的事務策略,適合從簡單應用程序到高性能事務處理等各種用例。

但是不關心事務的開發人員就會遇到麻煩。幾乎所有與業務相關的應用程序都需要高度的數據質量。金融投資行業在失敗的交易上浪費數百億美元,不好的數據是導致這種結果的第二大因素(請參閱?參考資料)。盡然缺少事務支持只是導致壞數據的一個因素(但是是主要的因素),但是完全可以這樣認為,在金融投資行業浪費掉數十億美元是由于缺少事務支持或事務支持不充分。

忽略事務支持是導致問題的另一個原因。我常常聽到 “我們的應用程序中不需要事務支持,因為這些應用程序從來不會失敗” 之類的說法。是的,我知道有些應用程序極少或從來不會拋出異常。這些應用程序基于編寫良好的代碼、編寫良好的驗證例程,并經過了充分的測試,有代碼覆蓋支持,可以避免性能損耗和與事務處理有關的復雜性。這種類型的應用程序只需考慮事務支持的一個特性:原子性。原子性確保所有更新被當作一個單獨的單元,要么全部提交,要么回滾。但是回滾或同時更新不是事務支持的惟一方面。另一方面,隔離性?將確保某一工作單元獨立于其他工作單元。沒有適當的事務隔離性,其他工作單元就可以訪問某一活動工作單元所做的更新,即使該工作單元還未完成。這樣,就會基于部分數據作出業務決策,而這會導致失敗的交易或產生其他負面(或代價昂貴的)結果。

遲做總比不做好

我是在 2000 年年初開始關注事務處理問題的,當時我正在研究一個客戶端站點,我發現項目計劃中有一項內容優先于系統測試任務。它稱為實現事務支持。當然,在某個主要應用程序差不多準備好進行系統測試時,給它添加事務支持是非常簡單的。遺憾的是,這種方法實在太普通。至少這個項目(與大多數項目不同)確實?實現了事務支持,盡管是在開發周期快結束時。

因此,考慮到壞數據的高成本和負面影響,以及事務的重要性(和必須性)這些基本常識,您需要使用事務處理并學習如何處理可能出現的問題。您在應用程序中添加事務支持后常常會出現很多問題。事務在 Java 平臺中并不總是如預想的那樣工作。本文會探討其中的原因。我將借助代碼示例,介紹一些我在該領域中不斷看到的和經歷的常見事務陷阱,大部分是在生產環境中。

雖然本文中的大多數代碼示例使用的是 Spring Framework(version 2.5),但事務概念與 EJB 3.0 規范中的是相同的。在大多數情況下,用 EJB 3.0 規范中的_cnnew1@TransactionAttribute?注釋替換 Spring Framework?@Transactional?注釋即可。如果這兩種框架使用了不同的概念和技術,我將同時給出 Spring Framework 和 EJB 3.0 源代碼示例。

本地事務陷阱

最好先從最簡單的場景開始,即使用本地事務,一般也稱為數據庫事務。在數據庫持久性的早期(例如 JDBC),我們一般會將事務處理委派給數據庫。畢竟這是數據庫應該做的。本地事務很適合執行單一插入、更新或刪除語句的邏輯工作單元(LUW)。例如,考慮清單 1 中的簡單 JDBC 代碼,它向?TRADE?表插入一份股票交易訂單:

清單 1. 使用 JDBC 的簡單數據庫插入
@Stateless public class TradingServiceImpl implements TradingService {@Resource SessionContext ctx;@Resource(mappedName="java:jdbc/tradingDS") DataSource ds;public long insertTrade(TradeData trade) throws Exception {Connection dbConnection = ds.getConnection();try {Statement sql = dbConnection.createStatement();String stmt ="INSERT INTO TRADE (ACCT_ID, SIDE, SYMBOL, SHARES, PRICE, STATE)"+ "VALUES ("+ trade.getAcct() + "','"+ trade.getAction() + "','"+ trade.getSymbol() + "',"+ trade.getShares() + ","+ trade.getPrice() + ",'"+ trade.getState() + "')";sql.executeUpdate(stmt, Statement.RETURN_GENERATED_KEYS);ResultSet rs = sql.getGeneratedKeys();if (rs.next()) {return rs.getBigDecimal(1).longValue();} else {throw new Exception("Trade Order Insert Failed");}} finally {if (dbConnection != null) dbConnection.close();}} }

清單 1 中的 JDBC 代碼沒有包含任何事務邏輯,它只是在數據庫中保存?TRADE?表中的交易訂單。在本例中,數據庫處理事務邏輯。

在 LUW 中,這是一個不錯的單個數據庫維護操作。但是如果需要在向數據庫插入交易訂單的同時更新帳戶余款呢?如清單 2 所示:

清單 2. 在同一方法中執行多次表更新
public TradeData placeTrade(TradeData trade) throws Exception {try {insertTrade(trade);updateAcct(trade);return trade;} catch (Exception up) {//log the errorthrow up;} }

在本例中,insertTrade()?和?updateAcct()?方法使用不帶事務的標準 JDBC 代碼。insertTrade()?方法結束后,數據庫保存(并提交了)交易訂單。如果?updateAcct()?方法由于任意原因失敗,交易訂單仍然會在?placeTrade()?方法結束時保存在?TRADE?表內,這會導致數據庫出現不一致的數據。如果?placeTrade()?方法使用了事務,這兩個活動都會包含在一個 LUW 中,如果帳戶更新失敗,交易訂單就會回滾。

隨著 Java 持久性框架的不斷普及,如 Hibernate、TopLink 和 Java 持久性 API(Java Persistence API,JPA),我們很少再會去編寫簡單的 JDBC 代碼。更常見的情況是,我們使用更新的對象關系映射(ORM)框架來減輕工作,即用幾個簡單的方法調用替換所有麻煩的 JDBC 代碼。例如,要插入?清單 1?中 JDBC 代碼示例的交易訂單,使用帶有 JPA 的 Spring Framework,就可以將?TradeData?對象映射到?TRADE?表,并用清單 3 中的 JPA 代碼替換所有 JDBC 代碼:

清單 3. 使用 JPA 的簡單插入
public class TradingServiceImpl {@PersistenceContext(unitName="trading") EntityManager em;public long insertTrade(TradeData trade) throws Exception {em.persist(trade);return trade.getTradeId();} }

注意,清單 3 在?EntityManager?上調用了?persist()?方法來插入交易訂單。很簡單,是吧?其實不然。這段代碼不會像預期那樣向?TRADE?表插入交易訂單,也不會拋出異常。它只是返回一個值?0?作為交易訂單的鍵,而不會更改數據庫。這是事務處理的主要陷阱之一:基于 ORM 的框架需要一個事務來觸發對象緩存與數據庫之間的同步。這通過一個事務提交完成,其中會生成 SQL 代碼,數據庫會執行需要的操作(即插入、更新、刪除)。沒有事務,就不會觸發 ORM 去生成 SQL 代碼和保存更改,因此只會終止方法 — 沒有異常,沒有更新。如果使用基于 ORM 的框架,就必須利用事務。您不再依賴數據庫來管理連接和提交工作。

這些簡單的示例應該清楚地說明,為了維護數據完整性和一致性,必須使用事務。不過對于在 Java 平臺中實現事務的復雜性和陷阱而言,這些示例只是涉及了冰山一角。

Spring Framework?@Transactional?注釋陷阱

您將測試?清單 3?中的代碼,發現?persist()?方法在沒有事務的情況下不能工作。因此,您通過簡單的網絡搜索查看幾個鏈接,發現如果使用 Spring Framework,就需要使用?@Transactional?注釋。于是您在代碼中添加該注釋,如清單 4 所示:

清單 4. 使用?@Transactional?注釋
public class TradingServiceImpl {@PersistenceContext(unitName="trading") EntityManager em;@Transactionalpublic long insertTrade(TradeData trade) throws Exception {em.persist(trade);return trade.getTradeId();} }

現在重新測試代碼,您發現上述方法仍然不能工作。問題在于您必須告訴 Spring Framework,您正在對事務管理應用注釋。除非您進行充分的單元測試,否則有時候很難發現這個陷阱。這通常只會導致開發人員在 Spring 配置文件中簡單地添加事務邏輯,而不會使用注釋。

要在 Spring 中使用?@Transactional?注釋,必須在 Spring 配置文件中添加以下代碼行:

<tx:annotation-driven transaction-manager="transactionManager"/>

transaction-manager?屬性保存一個對在 Spring 配置文件中定義的事務管理器 bean 的引用。這段代碼告訴 Spring 在應用事務攔截器時使用@Transaction?注釋。如果沒有它,就會忽略?@Transactional?注釋,導致代碼不會使用任何事務。

讓基本的?@Transactional?注釋在?清單 4?的代碼中工作僅僅是開始。注意,清單 4 使用?@Transactional?注釋時沒有指定任何額外的注釋參數。我發現許多開發人員在使用?@Transactional?注釋時并沒有花時間理解它的作用。例如,像我一樣在清單 4 中單獨使用?@Transactional注釋時,事務傳播模式被設置成什么呢?只讀標志被設置成什么呢?事務隔離級別的設置是怎樣的?更重要的是,事務應何時回滾工作?理解如何使用這個注釋對于確保在應用程序中獲得合適的事務支持級別非常重要。回答我剛才提出的問題:在單獨使用不帶任何參數的@Transactional?注釋時,傳播模式要設置為?REQUIRED,只讀標志設置為?false,事務隔離級別設置為?READ_COMMITTED,而且事務不會針對受控異常(checked exception)回滾。

@Transactional只讀標志陷阱

我在工作中經常碰到的一個常見陷阱是 Spring?@Transactional?注釋中的只讀標志沒有得到恰當使用。這里有一個快速測試方法:在使用標準 JDBC 代碼獲得 Java 持久性時,如果只讀標志設置為?true,傳播模式設置為?SUPPORTS,清單 5 中的?@Transactional?注釋的作用是什么呢?

清單 5. 將只讀標志與?SUPPORTS?傳播模式結合使用 — JDBC
@Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public long insertTrade(TradeData trade) throws Exception {//JDBC Code... }

當執行清單 5 中的?insertTrade()?方法時,猜一猜會得到下面哪一種結果:

  • 拋出一個只讀連接異常
  • 正確插入交易訂單并提交數據
  • 什么也不做,因為傳播級別被設置為?SUPPORTS

是哪一個呢?正確答案是 B。交易訂單會被正確地插入到數據庫中,即使只讀標志被設置為?true,且事務傳播模式被設置為?SUPPORTS。但這是如何做到的呢?由于傳播模式被設置為?SUPPORTS,所以不會啟動任何事物,因此該方法有效地利用了一個本地(數據庫)事務。只讀標志只在事務啟動時應用。在本例中,因為沒有啟動任何事務,所以只讀標志被忽略。

如果是這樣的話,清單 6 中的?@Transactional?注釋在設置了只讀標志且傳播模式被設置為?REQUIRED?時,它的作用是什么呢?

清單 6. 將只讀標志與?REQUIRED?傳播模式結合使用 — JDBC
@Transactional(readOnly = true, propagation=Propagation.REQUIRED) public long insertTrade(TradeData trade) throws Exception {//JDBC code... }

執行清單 6 中的?insertTrade()?方法會得到下面哪一種結果呢:

  • 拋出一個只讀連接異常
  • 正確插入交易訂單并提交數據
  • 什么也不做,因為只讀標志被設置為?true

根據前面的解釋,這個問題應該很好回答。正確的答案是 A。會拋出一個異常,表示您正在試圖對一個只讀連接執行更新。因為啟動了一個事務(REQUIRED),所以連接被設置為只讀。毫無疑問,在試圖執行 SQL 語句時,您會得到一個異常,告訴您該連接是一個只讀連接。

關于只讀標志很奇怪的一點是:要使用它,必須啟動一個事務。如果只是讀取數據,需要事務嗎?答案是根本不需要。啟動一個事務來執行只讀操作會增加處理線程的開銷,并會導致數據庫發生共享讀取鎖定(具體取決于使用的數據庫類型和設置的隔離級別)。總的來說,在獲取基于 JDBC 的 Java 持久性時,使用只讀標志有點毫無意義,并會啟動不必要的事務而增加額外的開銷。

使用基于 ORM 的框架會怎樣呢?按照上面的測試,如果在結合使用 JPA 和 Hibernate 時調用?insertTrade()?方法,清單 7 中的@Transactional?注釋會得到什么結果?

清單 7. 將只讀標志與?REQUIRED?傳播模式結合使用 — JPA
@Transactional(readOnly = true, propagation=Propagation.REQUIRED) public long insertTrade(TradeData trade) throws Exception {em.persist(trade);return trade.getTradeId(); }

清單 7 中的?insertTrade()?方法會得到下面哪一種結果:

  • 拋出一個只讀連接異常
  • 正確插入交易訂單并提交數據
  • 什么也不做,因為?readOnly?標志被設置為 true

正確的答案是 B。交易訂單會被準確無誤地插入數據庫中。請注意,上一示例表明,在使用?REQUIRED?傳播模式時,會拋出一個只讀連接異常。使用 JDBC 時是這樣。使用基于 ORM 的框架時,只讀標志只是對數據庫的一個提示,并且一條基于 ORM 框架的指令(本例中是 Hibernate)將對象緩存的 flush 模式設置為?NEVER,表示在這個工作單元中,該對象緩存不應與數據庫同步。不過,REQUIRED?傳播模式會覆蓋所有這些內容,允許事務啟動并工作,就好像沒有設置只讀標志一樣。

這令我想到了另一個我經常碰到的主要陷阱。閱讀了前面的所有內容后,您認為如果只對?@Transactional?注釋設置只讀標志,清單 8 中的代碼會得到什么結果呢?

清單 8. 使用只讀標志 — JPA
@Transactional(readOnly = true) public TradeData getTrade(long tradeId) throws Exception {return em.find(TradeData.class, tradeId); }

清單 8 中的?getTrade()?方法會執行以下哪一種操作?

  • 啟動一個事務,獲取交易訂單,然后提交事務
  • 獲取交易訂單,但不啟動事務

正確的答案是 A。一個事務會被啟動并提交。不要忘了,@Transactional?注釋的默認傳播模式是?REQUIRED。這意味著事務會在不必要的情況下啟動。根據使用的數據庫,這會引起不必要的共享鎖,可能會使數據庫中出現死鎖的情況。此外,啟動和停止事務將消耗不必要的處理時間和資源。總的來說,在使用基于 ORM 的框架時,只讀標志基本上毫無用處,在大多數情況下會被忽略。但如果您堅持使用它,請記得將傳播模式設置為?SUPPORTS(如清單 9 所示),這樣就不會啟動事務:

清單 9. 使用只讀標志和?SUPPORTS?傳播模式進行選擇操作
@Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public TradeData getTrade(long tradeId) throws Exception {return em.find(TradeData.class, tradeId); }

另外,在執行讀取操作時,避免使用?@Transactional?注釋,如清單 10 所示:

清單 10. 刪除?@Transactional?注釋進行選擇操作
public TradeData getTrade(long tradeId) throws Exception {return em.find(TradeData.class, tradeId); }

REQUIRES_NEW?事務屬性陷阱

不管是使用 Spring Framework,還是使用 EJB,使用?REQUIRES_NEW?事務屬性都會得到不好的結果并導致數據損壞和不一致。REQUIRES_NEW事務屬性總是會在啟動方法時啟動一個新的事務。許多開發人員都錯誤地使用?REQUIRES_NEW?屬性,認為它是確保事務啟動的正確方法。考慮清單 11 中的兩個方法:

清單 11. 使用?REQUIRES_NEW?事務屬性
@Transactional(propagation=Propagation.REQUIRES_NEW) public long insertTrade(TradeData trade) throws Exception {...}@Transactional(propagation=Propagation.REQUIRES_NEW) public void updateAcct(TradeData trade) throws Exception {...}

注意,清單 11 中的兩個方法都是公共方法,這意味著它們可以單獨調用。當使用?REQUIRES_NEW?屬性的幾個方法通過服務間通信或編排在同一邏輯工作單元內調用時,該屬性就會出現問題。例如,假設在清單 11 中,您可以獨立于一些用例中的任何其他方法來調用?updateAcct()?方法,但也有在?insertTrade()?方法中調用?updateAcct()?方法的情況。現在如果調用?updateAcct()?方法后拋出異常,交易訂單就會回滾,但帳戶更新將會提交給數據庫,如清單 12 所示:

清單 12. 使用?REQUIRES_NEW?事務屬性的多次更新
@Transactional(propagation=Propagation.REQUIRES_NEW) public long insertTrade(TradeData trade) throws Exception {em.persist(trade);updateAcct(trade);//exception occurs here! Trade rolled back but account update is not!... }

之所以會發生這種情況是因為?updateAcct()?方法中啟動了一個新事務,所以在?updateAcct()?方法結束后,事務將被提交。使用REQUIRES_NEW?事務屬性時,如果存在現有事務上下文,當前的事務會被掛起并啟動一個新事務。方法結束后,新的事務被提交,原來的事務繼續執行。

由于這種行為,只有在被調用方法中的數據庫操作需要保存到數據庫中,而不管覆蓋事務的結果如何時,才應該使用?REQUIRES_NEW?事務屬性。比如,假設嘗試的所有股票交易都必須被記錄在一個審計數據庫中。出于驗證錯誤、資金不足或其他原因,不管交易是否失敗,這條信息都需要被持久化。如果沒有對審計方法使用?REQUIRES_NEW?屬性,審計記錄就會連同嘗試執行的交易一起回滾。使用?REQUIRES_NEW?屬性可以確保不管初始事務的結果如何,審計數據都會被保存。這里要注意的一點是,要始終使用?MANDATORY?或?REQUIRED?屬性,而不是REQUIRES_NEW,除非您有足夠的理由來使用它,類似審計示例中的那些理由。

事務回滾陷阱

我將最常見的事務陷阱留到最后來講。遺憾的是,我在生產代碼中多次???到這個錯誤。我首先從 Spring Framework 開始,然后介紹 EJB 3。

到目前為止,您研究的代碼類似清單 13 所示:

清單 13. 沒有回滾支持
@Transactional(propagation=Propagation.REQUIRED) public TradeData placeTrade(TradeData trade) throws Exception {try {insertTrade(trade);updateAcct(trade);return trade;} catch (Exception up) {//log the errorthrow up;} }

假設帳戶中沒有足夠的資金來購買需要的股票,或者還沒有準備購買或出售股票,并拋出了一個受控異常(例如FundsNotAvailableException),那么交易訂單會保存在數據庫中嗎?還是整個邏輯工作單元將執行回滾?答案出乎意料:根據受控異常(不管是在 Spring Framework 中還是在 EJB 中),事務會提交它還未提交的所有工作。使用清單 13,這意味著,如果在執行?updateAcct()方法期間拋出受控異常,就會保存交易訂單,但不會更新帳戶來反映交易情況。

這可能是在使用事務時出現的主要數據完整性和一致性問題了。運行時異常(即非受控異常)自動強制執行整個邏輯工作單元的回滾,但受控異常不會。因此,清單 13 中的代碼從事務角度來說毫無用處;盡管看上去它使用事務來維護原子性和一致性,但事實上并沒有。

盡管這種行為看起來很奇怪,但這樣做自有它的道理。首先,不是所有受控異常都是不好的;它們可用于事件通知或根據某些條件重定向處理。但更重要的是,應用程序代碼會對某些類型的受控異常采取糾正操作,從而使事務全部完成。例如,考慮下面一種場景:您正在為在線書籍零售商編寫代碼。要完成圖書的訂單,您需要將電子郵件形式的確認函作為訂單處理的一部分發送。如果電子郵件服務器關閉,您將發送某種形式的 SMTP 受控異常,表示郵件無法發送。如果受控異常引起自動回滾,整個圖書訂單就會由于電子郵件服務器的關閉全部回滾。通過禁止自動回滾受控異常,您可以捕獲該異常并執行某種糾正操作(如向掛起隊列發送消息),然后提交剩余的訂單。

使用 Declarative 事務模式(本系列的第 2 部分將進行更加詳細的描述)時,必須指定容器或框架應該如何處理受控異常。在 Spring Framework 中,通過?@Transactional?注釋中的?rollbackFor?參數進行指定,如清單 14 所示:

清單 14. 添加事務回滾支持 — Spring
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class) public TradeData placeTrade(TradeData trade) throws Exception {try {insertTrade(trade);updateAcct(trade);return trade;} catch (Exception up) {//log the errorthrow up;} }

注意,@Transactional?注釋中使用了?rollbackFor?參數。這個參數接受一個單一異常類或一組異常類,您也可以使用rollbackForClassName?參數將異常的名稱指定為 Java?String?類型。還可以使用此屬性的相反形式(noRollbackFor)指定除某些異常以外的所有異常應該強制回滾。通常大多數開發人員指定?Exception.class?作為值,表示該方法中的所有異常應該強制回滾。

在回滾事務這一點上,EJB 的工作方式與 Spring Framework 稍微有點不同。EJB 3.0 規范中的?@TransactionAttribute?注釋不包含指定回滾行為的指令。必須使用?SessionContext.setRollbackOnly()?方法將事務標記為執行回滾,如清單 15 所示:

清單 15. 添加事務回滾支持 — EJB
@TransactionAttribute(TransactionAttributeType.REQUIRED) public TradeData placeTrade(TradeData trade) throws Exception {try {insertTrade(trade);updateAcct(trade);return trade;} catch (Exception up) {//log the errorsessionCtx.setRollbackOnly();throw up;} }

調用?setRollbackOnly()?方法后,就不能改變主意了;惟一可能的結果是在啟動事務的方法完成后回滾事務。本系列后續文章中描述的事務策略將介紹何時、何處使用回滾指令,以及何時使用?REQUIRED?與?MANDATORY?事務屬性。

結束語

用于在 Java 平臺中實現事務的代碼不是太復雜;但是,如何使用以及如何配置它就有一些復雜了。在 Java 平臺中實現事務支持有許多陷阱(包括一些我未在本文中討論的、不是很常見的陷阱)。大多數陷阱最大的問題是,不會有任何編譯器警告或運行時錯誤告訴您事務實現是不正確的。而且,與本文開頭的 “遲做總比不做好” 部分的內容相反,實現事務支持不僅僅是一個編碼工作。開發一個完整的事務策略涉及大量的設計工作。事務策略?系列的其余部分將指導您如何設計針對從簡單應用程序到高性能事務處理用例的有效事務策略。

原文:http://www.ibm.com/developerworks/cn/java/j-ts1.html

轉載于:https://www.cnblogs.com/davidwang456/p/3833492.html

總結

以上是生活随笔為你收集整理的事务策略: 了解事务陷阱--转的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。