事务策略: 了解事务陷阱--转
在 Java 平臺中實現(xiàn)事務(wù)時要注意的常見錯誤
在應(yīng)用程序中使用事務(wù)常常是為了維護(hù)高度的數(shù)據(jù)完整性和一致性。如果不關(guān)心數(shù)據(jù)的質(zhì)量,就不必使用事務(wù)。畢竟,Java 平臺中的事務(wù)支持會降低性能,引發(fā)鎖定問題和數(shù)據(jù)庫并發(fā)性問題,而且會增加應(yīng)用程序的復(fù)雜性。
關(guān)于本系列
事務(wù)提高了數(shù)據(jù)的質(zhì)量、完整性和一致性,使應(yīng)用程序更健壯。在 Java 應(yīng)用程序中實現(xiàn)成功的事務(wù)處理不是一件容易的事,設(shè)計和編碼幾乎一樣重要。在這份新的?系列文章?中,Mark Richards 將帶領(lǐng)您設(shè)計一個有效的事務(wù)策略,適合從簡單應(yīng)用程序到高性能事務(wù)處理等各種用例。
但是不關(guān)心事務(wù)的開發(fā)人員就會遇到麻煩。幾乎所有與業(yè)務(wù)相關(guān)的應(yīng)用程序都需要高度的數(shù)據(jù)質(zhì)量。金融投資行業(yè)在失敗的交易上浪費數(shù)百億美元,不好的數(shù)據(jù)是導(dǎo)致這種結(jié)果的第二大因素(請參閱?參考資料)。盡然缺少事務(wù)支持只是導(dǎo)致壞數(shù)據(jù)的一個因素(但是是主要的因素),但是完全可以這樣認(rèn)為,在金融投資行業(yè)浪費掉數(shù)十億美元是由于缺少事務(wù)支持或事務(wù)支持不充分。
忽略事務(wù)支持是導(dǎo)致問題的另一個原因。我常常聽到 “我們的應(yīng)用程序中不需要事務(wù)支持,因為這些應(yīng)用程序從來不會失敗” 之類的說法。是的,我知道有些應(yīng)用程序極少或從來不會拋出異常。這些應(yīng)用程序基于編寫良好的代碼、編寫良好的驗證例程,并經(jīng)過了充分的測試,有代碼覆蓋支持,可以避免性能損耗和與事務(wù)處理有關(guān)的復(fù)雜性。這種類型的應(yīng)用程序只需考慮事務(wù)支持的一個特性:原子性。原子性確保所有更新被當(dāng)作一個單獨的單元,要么全部提交,要么回滾。但是回滾或同時更新不是事務(wù)支持的惟一方面。另一方面,隔離性?將確保某一工作單元獨立于其他工作單元。沒有適當(dāng)?shù)氖聞?wù)隔離性,其他工作單元就可以訪問某一活動工作單元所做的更新,即使該工作單元還未完成。這樣,就會基于部分?jǐn)?shù)據(jù)作出業(yè)務(wù)決策,而這會導(dǎo)致失敗的交易或產(chǎn)生其他負(fù)面(或代價昂貴的)結(jié)果。
遲做總比不做好
我是在 2000 年年初開始關(guān)注事務(wù)處理問題的,當(dāng)時我正在研究一個客戶端站點,我發(fā)現(xiàn)項目計劃中有一項內(nèi)容優(yōu)先于系統(tǒng)測試任務(wù)。它稱為實現(xiàn)事務(wù)支持。當(dāng)然,在某個主要應(yīng)用程序差不多準(zhǔn)備好進(jìn)行系統(tǒng)測試時,給它添加事務(wù)支持是非常簡單的。遺憾的是,這種方法實在太普通。至少這個項目(與大多數(shù)項目不同)確實?實現(xiàn)了事務(wù)支持,盡管是在開發(fā)周期快結(jié)束時。
因此,考慮到壞數(shù)據(jù)的高成本和負(fù)面影響,以及事務(wù)的重要性(和必須性)這些基本常識,您需要使用事務(wù)處理并學(xué)習(xí)如何處理可能出現(xiàn)的問題。您在應(yīng)用程序中添加事務(wù)支持后常常會出現(xiàn)很多問題。事務(wù)在 Java 平臺中并不總是如預(yù)想的那樣工作。本文會探討其中的原因。我將借助代碼示例,介紹一些我在該領(lǐng)域中不斷看到的和經(jīng)歷的常見事務(wù)陷阱,大部分是在生產(chǎn)環(huán)境中。
雖然本文中的大多數(shù)代碼示例使用的是 Spring Framework(version 2.5),但事務(wù)概念與 EJB 3.0 規(guī)范中的是相同的。在大多數(shù)情況下,用 EJB 3.0 規(guī)范中的_cnnew1@TransactionAttribute?注釋替換 Spring Framework?@Transactional?注釋即可。如果這兩種框架使用了不同的概念和技術(shù),我將同時給出 Spring Framework 和 EJB 3.0 源代碼示例。
本地事務(wù)陷阱
最好先從最簡單的場景開始,即使用本地事務(wù),一般也稱為數(shù)據(jù)庫事務(wù)。在數(shù)據(jù)庫持久性的早期(例如 JDBC),我們一般會將事務(wù)處理委派給數(shù)據(jù)庫。畢竟這是數(shù)據(jù)庫應(yīng)該做的。本地事務(wù)很適合執(zhí)行單一插入、更新或刪除語句的邏輯工作單元(LUW)。例如,考慮清單 1 中的簡單 JDBC 代碼,它向?TRADE?表插入一份股票交易訂單:
清單 1. 使用 JDBC 的簡單數(shù)據(jù)庫插入
@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 代碼沒有包含任何事務(wù)邏輯,它只是在數(shù)據(jù)庫中保存?TRADE?表中的交易訂單。在本例中,數(shù)據(jù)庫處理事務(wù)邏輯。
在 LUW 中,這是一個不錯的單個數(shù)據(jù)庫維護(hù)操作。但是如果需要在向數(shù)據(jù)庫插入交易訂單的同時更新帳戶余款呢?如清單 2 所示:
清單 2. 在同一方法中執(zhí)行多次表更新
public TradeData placeTrade(TradeData trade) throws Exception {try {insertTrade(trade);updateAcct(trade);return trade;} catch (Exception up) {//log the errorthrow up;} }在本例中,insertTrade()?和?updateAcct()?方法使用不帶事務(wù)的標(biāo)準(zhǔn) JDBC 代碼。insertTrade()?方法結(jié)束后,數(shù)據(jù)庫保存(并提交了)交易訂單。如果?updateAcct()?方法由于任意原因失敗,交易訂單仍然會在?placeTrade()?方法結(jié)束時保存在?TRADE?表內(nèi),這會導(dǎo)致數(shù)據(jù)庫出現(xiàn)不一致的數(shù)據(jù)。如果?placeTrade()?方法使用了事務(wù),這兩個活動都會包含在一個 LUW 中,如果帳戶更新失敗,交易訂單就會回滾。
隨著 Java 持久性框架的不斷普及,如 Hibernate、TopLink 和 Java 持久性 API(Java Persistence API,JPA),我們很少再會去編寫簡單的 JDBC 代碼。更常見的情況是,我們使用更新的對象關(guān)系映射(ORM)框架來減輕工作,即用幾個簡單的方法調(diào)用替換所有麻煩的 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?上調(diào)用了?persist()?方法來插入交易訂單。很簡單,是吧?其實不然。這段代碼不會像預(yù)期那樣向?TRADE?表插入交易訂單,也不會拋出異常。它只是返回一個值?0?作為交易訂單的鍵,而不會更改數(shù)據(jù)庫。這是事務(wù)處理的主要陷阱之一:基于 ORM 的框架需要一個事務(wù)來觸發(fā)對象緩存與數(shù)據(jù)庫之間的同步。這通過一個事務(wù)提交完成,其中會生成 SQL 代碼,數(shù)據(jù)庫會執(zhí)行需要的操作(即插入、更新、刪除)。沒有事務(wù),就不會觸發(fā) ORM 去生成 SQL 代碼和保存更改,因此只會終止方法 — 沒有異常,沒有更新。如果使用基于 ORM 的框架,就必須利用事務(wù)。您不再依賴數(shù)據(jù)庫來管理連接和提交工作。
這些簡單的示例應(yīng)該清楚地說明,為了維護(hù)數(shù)據(jù)完整性和一致性,必須使用事務(wù)。不過對于在 Java 平臺中實現(xiàn)事務(wù)的復(fù)雜性和陷阱而言,這些示例只是涉及了冰山一角。
回頁首
Spring Framework?@Transactional?注釋陷阱
您將測試?清單 3?中的代碼,發(fā)現(xiàn)?persist()?方法在沒有事務(wù)的情況下不能工作。因此,您通過簡單的網(wǎng)絡(luò)搜索查看幾個鏈接,發(fā)現(xiàn)如果使用 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();} }現(xiàn)在重新測試代碼,您發(fā)現(xiàn)上述方法仍然不能工作。問題在于您必須告訴 Spring Framework,您正在對事務(wù)管理應(yīng)用注釋。除非您進(jìn)行充分的單元測試,否則有時候很難發(fā)現(xiàn)這個陷阱。這通常只會導(dǎo)致開發(fā)人員在 Spring 配置文件中簡單地添加事務(wù)邏輯,而不會使用注釋。
要在 Spring 中使用?@Transactional?注釋,必須在 Spring 配置文件中添加以下代碼行:
<tx:annotation-driven transaction-manager="transactionManager"/>transaction-manager?屬性保存一個對在 Spring 配置文件中定義的事務(wù)管理器 bean 的引用。這段代碼告訴 Spring 在應(yīng)用事務(wù)攔截器時使用@Transaction?注釋。如果沒有它,就會忽略?@Transactional?注釋,導(dǎo)致代碼不會使用任何事務(wù)。
讓基本的?@Transactional?注釋在?清單 4?的代碼中工作僅僅是開始。注意,清單 4 使用?@Transactional?注釋時沒有指定任何額外的注釋參數(shù)。我發(fā)現(xiàn)許多開發(fā)人員在使用?@Transactional?注釋時并沒有花時間理解它的作用。例如,像我一樣在清單 4 中單獨使用?@Transactional注釋時,事務(wù)傳播模式被設(shè)置成什么呢?只讀標(biāo)志被設(shè)置成什么呢?事務(wù)隔離級別的設(shè)置是怎樣的?更重要的是,事務(wù)應(yīng)何時回滾工作?理解如何使用這個注釋對于確保在應(yīng)用程序中獲得合適的事務(wù)支持級別非常重要?;卮鹞覄偛盘岢龅膯栴}:在單獨使用不帶任何參數(shù)的@Transactional?注釋時,傳播模式要設(shè)置為?REQUIRED,只讀標(biāo)志設(shè)置為?false,事務(wù)隔離級別設(shè)置為?READ_COMMITTED,而且事務(wù)不會針對受控異常(checked exception)回滾。
回頁首
@Transactional只讀標(biāo)志陷阱
我在工作中經(jīng)常碰到的一個常見陷阱是 Spring?@Transactional?注釋中的只讀標(biāo)志沒有得到恰當(dāng)使用。這里有一個快速測試方法:在使用標(biāo)準(zhǔn) JDBC 代碼獲得 Java 持久性時,如果只讀標(biāo)志設(shè)置為?true,傳播模式設(shè)置為?SUPPORTS,清單 5 中的?@Transactional?注釋的作用是什么呢?
清單 5. 將只讀標(biāo)志與?SUPPORTS?傳播模式結(jié)合使用 — JDBC
@Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public long insertTrade(TradeData trade) throws Exception {//JDBC Code... }當(dāng)執(zhí)行清單 5 中的?insertTrade()?方法時,猜一猜會得到下面哪一種結(jié)果:
- 拋出一個只讀連接異常
- 正確插入交易訂單并提交數(shù)據(jù)
- 什么也不做,因為傳播級別被設(shè)置為?SUPPORTS
是哪一個呢?正確答案是 B。交易訂單會被正確地插入到數(shù)據(jù)庫中,即使只讀標(biāo)志被設(shè)置為?true,且事務(wù)傳播模式被設(shè)置為?SUPPORTS。但這是如何做到的呢?由于傳播模式被設(shè)置為?SUPPORTS,所以不會啟動任何事物,因此該方法有效地利用了一個本地(數(shù)據(jù)庫)事務(wù)。只讀標(biāo)志只在事務(wù)啟動時應(yīng)用。在本例中,因為沒有啟動任何事務(wù),所以只讀標(biāo)志被忽略。
如果是這樣的話,清單 6 中的?@Transactional?注釋在設(shè)置了只讀標(biāo)志且傳播模式被設(shè)置為?REQUIRED?時,它的作用是什么呢?
清單 6. 將只讀標(biāo)志與?REQUIRED?傳播模式結(jié)合使用 — JDBC
@Transactional(readOnly = true, propagation=Propagation.REQUIRED) public long insertTrade(TradeData trade) throws Exception {//JDBC code... }執(zhí)行清單 6 中的?insertTrade()?方法會得到下面哪一種結(jié)果呢:
- 拋出一個只讀連接異常
- 正確插入交易訂單并提交數(shù)據(jù)
- 什么也不做,因為只讀標(biāo)志被設(shè)置為?true
根據(jù)前面的解釋,這個問題應(yīng)該很好回答。正確的答案是 A。會拋出一個異常,表示您正在試圖對一個只讀連接執(zhí)行更新。因為啟動了一個事務(wù)(REQUIRED),所以連接被設(shè)置為只讀。毫無疑問,在試圖執(zhí)行 SQL 語句時,您會得到一個異常,告訴您該連接是一個只讀連接。
關(guān)于只讀標(biāo)志很奇怪的一點是:要使用它,必須啟動一個事務(wù)。如果只是讀取數(shù)據(jù),需要事務(wù)嗎?答案是根本不需要。啟動一個事務(wù)來執(zhí)行只讀操作會增加處理線程的開銷,并會導(dǎo)致數(shù)據(jù)庫發(fā)生共享讀取鎖定(具體取決于使用的數(shù)據(jù)庫類型和設(shè)置的隔離級別)。總的來說,在獲取基于 JDBC 的 Java 持久性時,使用只讀標(biāo)志有點毫無意義,并會啟動不必要的事務(wù)而增加額外的開銷。
使用基于 ORM 的框架會怎樣呢?按照上面的測試,如果在結(jié)合使用 JPA 和 Hibernate 時調(diào)用?insertTrade()?方法,清單 7 中的@Transactional?注釋會得到什么結(jié)果?
清單 7. 將只讀標(biāo)志與?REQUIRED?傳播模式結(jié)合使用 — JPA
@Transactional(readOnly = true, propagation=Propagation.REQUIRED) public long insertTrade(TradeData trade) throws Exception {em.persist(trade);return trade.getTradeId(); }清單 7 中的?insertTrade()?方法會得到下面哪一種結(jié)果:
- 拋出一個只讀連接異常
- 正確插入交易訂單并提交數(shù)據(jù)
- 什么也不做,因為?readOnly?標(biāo)志被設(shè)置為 true
正確的答案是 B。交易訂單會被準(zhǔn)確無誤地插入數(shù)據(jù)庫中。請注意,上一示例表明,在使用?REQUIRED?傳播模式時,會拋出一個只讀連接異常。使用 JDBC 時是這樣。使用基于 ORM 的框架時,只讀標(biāo)志只是對數(shù)據(jù)庫的一個提示,并且一條基于 ORM 框架的指令(本例中是 Hibernate)將對象緩存的 flush 模式設(shè)置為?NEVER,表示在這個工作單元中,該對象緩存不應(yīng)與數(shù)據(jù)庫同步。不過,REQUIRED?傳播模式會覆蓋所有這些內(nèi)容,允許事務(wù)啟動并工作,就好像沒有設(shè)置只讀標(biāo)志一樣。
這令我想到了另一個我經(jīng)常碰到的主要陷阱。閱讀了前面的所有內(nèi)容后,您認(rèn)為如果只對?@Transactional?注釋設(shè)置只讀標(biāo)志,清單 8 中的代碼會得到什么結(jié)果呢?
清單 8. 使用只讀標(biāo)志 — JPA
@Transactional(readOnly = true) public TradeData getTrade(long tradeId) throws Exception {return em.find(TradeData.class, tradeId); }清單 8 中的?getTrade()?方法會執(zhí)行以下哪一種操作?
- 啟動一個事務(wù),獲取交易訂單,然后提交事務(wù)
- 獲取交易訂單,但不啟動事務(wù)
正確的答案是 A。一個事務(wù)會被啟動并提交。不要忘了,@Transactional?注釋的默認(rèn)傳播模式是?REQUIRED。這意味著事務(wù)會在不必要的情況下啟動。根據(jù)使用的數(shù)據(jù)庫,這會引起不必要的共享鎖,可能會使數(shù)據(jù)庫中出現(xiàn)死鎖的情況。此外,啟動和停止事務(wù)將消耗不必要的處理時間和資源。總的來說,在使用基于 ORM 的框架時,只讀標(biāo)志基本上毫無用處,在大多數(shù)情況下會被忽略。但如果您堅持使用它,請記得將傳播模式設(shè)置為?SUPPORTS(如清單 9 所示),這樣就不會啟動事務(wù):
清單 9. 使用只讀標(biāo)志和?SUPPORTS?傳播模式進(jìn)行選擇操作
@Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public TradeData getTrade(long tradeId) throws Exception {return em.find(TradeData.class, tradeId); }另外,在執(zhí)行讀取操作時,避免使用?@Transactional?注釋,如清單 10 所示:
清單 10. 刪除?@Transactional?注釋進(jìn)行選擇操作
public TradeData getTrade(long tradeId) throws Exception {return em.find(TradeData.class, tradeId); }回頁首
REQUIRES_NEW?事務(wù)屬性陷阱
不管是使用 Spring Framework,還是使用 EJB,使用?REQUIRES_NEW?事務(wù)屬性都會得到不好的結(jié)果并導(dǎo)致數(shù)據(jù)損壞和不一致。REQUIRES_NEW事務(wù)屬性總是會在啟動方法時啟動一個新的事務(wù)。許多開發(fā)人員都錯誤地使用?REQUIRES_NEW?屬性,認(rèn)為它是確保事務(wù)啟動的正確方法??紤]清單 11 中的兩個方法:
清單 11. 使用?REQUIRES_NEW?事務(wù)屬性
@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 中的兩個方法都是公共方法,這意味著它們可以單獨調(diào)用。當(dāng)使用?REQUIRES_NEW?屬性的幾個方法通過服務(wù)間通信或編排在同一邏輯工作單元內(nèi)調(diào)用時,該屬性就會出現(xiàn)問題。例如,假設(shè)在清單 11 中,您可以獨立于一些用例中的任何其他方法來調(diào)用?updateAcct()?方法,但也有在?insertTrade()?方法中調(diào)用?updateAcct()?方法的情況。現(xiàn)在如果調(diào)用?updateAcct()?方法后拋出異常,交易訂單就會回滾,但帳戶更新將會提交給數(shù)據(jù)庫,如清單 12 所示:
清單 12. 使用?REQUIRES_NEW?事務(wù)屬性的多次更新
@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!... }之所以會發(fā)生這種情況是因為?updateAcct()?方法中啟動了一個新事務(wù),所以在?updateAcct()?方法結(jié)束后,事務(wù)將被提交。使用REQUIRES_NEW?事務(wù)屬性時,如果存在現(xiàn)有事務(wù)上下文,當(dāng)前的事務(wù)會被掛起并啟動一個新事務(wù)。方法結(jié)束后,新的事務(wù)被提交,原來的事務(wù)繼續(xù)執(zhí)行。
由于這種行為,只有在被調(diào)用方法中的數(shù)據(jù)庫操作需要保存到數(shù)據(jù)庫中,而不管覆蓋事務(wù)的結(jié)果如何時,才應(yīng)該使用?REQUIRES_NEW?事務(wù)屬性。比如,假設(shè)嘗試的所有股票交易都必須被記錄在一個審計數(shù)據(jù)庫中。出于驗證錯誤、資金不足或其他原因,不管交易是否失敗,這條信息都需要被持久化。如果沒有對審計方法使用?REQUIRES_NEW?屬性,審計記錄就會連同嘗試執(zhí)行的交易一起回滾。使用?REQUIRES_NEW?屬性可以確保不管初始事務(wù)的結(jié)果如何,審計數(shù)據(jù)都會被保存。這里要注意的一點是,要始終使用?MANDATORY?或?REQUIRED?屬性,而不是REQUIRES_NEW,除非您有足夠的理由來使用它,類似審計示例中的那些理由。
回頁首
事務(wù)回滾陷阱
我將最常見的事務(wù)陷阱留到最后來講。遺憾的是,我在生產(chǎn)代碼中多次???到這個錯誤。我首先從 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;} }假設(shè)帳戶中沒有足夠的資金來購買需要的股票,或者還沒有準(zhǔn)備購買或出售股票,并拋出了一個受控異常(例如FundsNotAvailableException),那么交易訂單會保存在數(shù)據(jù)庫中嗎?還是整個邏輯工作單元將執(zhí)行回滾?答案出乎意料:根據(jù)受控異常(不管是在 Spring Framework 中還是在 EJB 中),事務(wù)會提交它還未提交的所有工作。使用清單 13,這意味著,如果在執(zhí)行?updateAcct()方法期間拋出受控異常,就會保存交易訂單,但不會更新帳戶來反映交易情況。
這可能是在使用事務(wù)時出現(xiàn)的主要數(shù)據(jù)完整性和一致性問題了。運行時異常(即非受控異常)自動強(qiáng)制執(zhí)行整個邏輯工作單元的回滾,但受控異常不會。因此,清單 13 中的代碼從事務(wù)角度來說毫無用處;盡管看上去它使用事務(wù)來維護(hù)原子性和一致性,但事實上并沒有。
盡管這種行為看起來很奇怪,但這樣做自有它的道理。首先,不是所有受控異常都是不好的;它們可用于事件通知或根據(jù)某些條件重定向處理。但更重要的是,應(yīng)用程序代碼會對某些類型的受控異常采取糾正操作,從而使事務(wù)全部完成。例如,考慮下面一種場景:您正在為在線書籍零售商編寫代碼。要完成圖書的訂單,您需要將電子郵件形式的確認(rèn)函作為訂單處理的一部分發(fā)送。如果電子郵件服務(wù)器關(guān)閉,您將發(fā)送某種形式的 SMTP 受控異常,表示郵件無法發(fā)送。如果受控異常引起自動回滾,整個圖書訂單就會由于電子郵件服務(wù)器的關(guān)閉全部回滾。通過禁止自動回滾受控異常,您可以捕獲該異常并執(zhí)行某種糾正操作(如向掛起隊列發(fā)送消息),然后提交剩余的訂單。
使用 Declarative 事務(wù)模式(本系列的第 2 部分將進(jìn)行更加詳細(xì)的描述)時,必須指定容器或框架應(yīng)該如何處理受控異常。在 Spring Framework 中,通過?@Transactional?注釋中的?rollbackFor?參數(shù)進(jìn)行指定,如清單 14 所示:
清單 14. 添加事務(wù)回滾支持 — 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?參數(shù)。這個參數(shù)接受一個單一異常類或一組異常類,您也可以使用rollbackForClassName?參數(shù)將異常的名稱指定為 Java?String?類型。還可以使用此屬性的相反形式(noRollbackFor)指定除某些異常以外的所有異常應(yīng)該強(qiáng)制回滾。通常大多數(shù)開發(fā)人員指定?Exception.class?作為值,表示該方法中的所有異常應(yīng)該強(qiáng)制回滾。
在回滾事務(wù)這一點上,EJB 的工作方式與 Spring Framework 稍微有點不同。EJB 3.0 規(guī)范中的?@TransactionAttribute?注釋不包含指定回滾行為的指令。必須使用?SessionContext.setRollbackOnly()?方法將事務(wù)標(biāo)記為執(zhí)行回滾,如清單 15 所示:
清單 15. 添加事務(wù)回滾支持 — 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;} }調(diào)用?setRollbackOnly()?方法后,就不能改變主意了;惟一可能的結(jié)果是在啟動事務(wù)的方法完成后回滾事務(wù)。本系列后續(xù)文章中描述的事務(wù)策略將介紹何時、何處使用回滾指令,以及何時使用?REQUIRED?與?MANDATORY?事務(wù)屬性。
回頁首
結(jié)束語
用于在 Java 平臺中實現(xiàn)事務(wù)的代碼不是太復(fù)雜;但是,如何使用以及如何配置它就有一些復(fù)雜了。在 Java 平臺中實現(xiàn)事務(wù)支持有許多陷阱(包括一些我未在本文中討論的、不是很常見的陷阱)。大多數(shù)陷阱最大的問題是,不會有任何編譯器警告或運行時錯誤告訴您事務(wù)實現(xiàn)是不正確的。而且,與本文開頭的 “遲做總比不做好” 部分的內(nèi)容相反,實現(xiàn)事務(wù)支持不僅僅是一個編碼工作。開發(fā)一個完整的事務(wù)策略涉及大量的設(shè)計工作。事務(wù)策略?系列的其余部分將指導(dǎo)您如何設(shè)計針對從簡單應(yīng)用程序到高性能事務(wù)處理用例的有效事務(wù)策略。
原文:http://www.ibm.com/developerworks/cn/java/j-ts1.html
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/3833492.html
總結(jié)
以上是生活随笔為你收集整理的事务策略: 了解事务陷阱--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实例详解 EJB 中的六大事务传播属性-
- 下一篇: java获取当前方法