<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    瘋狂

    STANDING ON THE SHOULDERS OF GIANTS
    posts - 481, comments - 486, trackbacks - 0, articles - 1
      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

    事務(wù)策略: 了解事務(wù)陷阱

    Posted on 2010-10-11 23:06 瘋狂 閱讀(1187) 評(píng)論(0)  編輯  收藏 所屬分類: java spring

    前沿(筆者加):事務(wù)(Transaction)是每一個(gè)與數(shù)據(jù)庫有關(guān)的系統(tǒng)開發(fā)與設(shè)計(jì)人員都會(huì)接觸到的東西,在Java中,傳統(tǒng)的直接使用JDBC的事務(wù)開始、提交、回滾的方式已經(jīng)隨著各種應(yīng)用開發(fā)框架(尤其是Spring)的出現(xiàn)變得對(duì)開發(fā)人員更加簡單,開發(fā)人員直接將事務(wù)的處理交由spring處理,只關(guān)心具體業(yè)務(wù),但是實(shí)際上Spring對(duì)事務(wù)的控制有自己的一套體系(筆者在Spring配置中transactionAttributes的意義一文中,曾介紹spring關(guān)于事務(wù)的部分配置及說明),如果開發(fā)設(shè)計(jì)人員對(duì)其沒有深入了解,很容易出現(xiàn)事務(wù)沒有正常提交,甚至出現(xiàn)事務(wù)不提交的情況,今天發(fā)現(xiàn)本文對(duì)于事務(wù)及其在Spring、EJB中的應(yīng)用,詳細(xì)介紹了如何正確使用事務(wù),希望可以對(duì)讀者有幫助。

    事務(wù)策略: 了解事務(wù)陷阱
    在 Java 平臺(tái)中實(shí)現(xiàn)事務(wù)時(shí)要注意的常見錯(cuò)誤

    級(jí)別: 中級(jí)

    Mark Richards, 主管和高級(jí)技術(shù)架構(gòu)師, Collaborative Consulting, LLC

    2009 年 3 月 06 日

    事務(wù)處理的目標(biāo)應(yīng)該是實(shí)現(xiàn)數(shù)據(jù)的高度完整性和一致性。本文是為 Java 平臺(tái)開發(fā)有效事務(wù)策略 系列文章 的第一篇,介紹了一些妨礙您實(shí)現(xiàn)此目標(biāo)的常見事務(wù)陷阱。本系列作者 Mark Richards 通過使用 Spring Framework 和企業(yè) JavaBeans(Enterprise JavaBeans,EJB)3.0 規(guī)范中的代碼示例解釋了這些極其常見的錯(cuò)誤。
    在應(yīng)用程序中使用事務(wù)常常是為了維護(hù)高度的數(shù)據(jù)完整性和一致性。如果不關(guān)心數(shù)據(jù)的質(zhì)量,就不必使用事務(wù)。畢竟,Java 平臺(tái)中的事務(wù)支持會(huì)降低性能,引發(fā)鎖定問題和數(shù)據(jù)庫并發(fā)性問題,而且會(huì)增加應(yīng)用程序的復(fù)雜性。

    但是不關(guān)心事務(wù)的開發(fā)人員就會(huì)遇到麻煩。幾乎所有與業(yè)務(wù)相關(guān)的應(yīng)用程序都需要高度的數(shù)據(jù)質(zhì)量。金融投資行業(yè)在失敗的交易上浪費(fèi)數(shù)百億美元,不好的數(shù)據(jù)是導(dǎo)致這種結(jié)果的第二大因素(請(qǐng)參閱 參考資料)。盡然缺少事務(wù)支持只是導(dǎo)致壞數(shù)據(jù)的一個(gè)因素(但是是主要的因素),但是完全可以這樣認(rèn)為,在金融投資行業(yè)浪費(fèi)掉數(shù)十億美元是由于缺少事務(wù)支持或事務(wù)支持不充分。

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

    因此,考慮到壞數(shù)據(jù)的高成本和負(fù)面影響,以及事務(wù)的重要性(和必須性)這些基本常識(shí),您需要使用事務(wù)處理并學(xué)習(xí)如何處理可能出現(xiàn)的問題。您在應(yīng)用程序中添加事務(wù)支持后常常會(huì)出現(xiàn)很多問題。事務(wù)在 Java 平臺(tái)中并不總是如預(yù)想的那樣工作。本文會(huì)探討其中的原因。我將借助代碼示例,介紹一些我在該領(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ù),我將同時(shí)給出 Spring Framework 和 EJB 3.0 源代碼示例。

    本地事務(wù)陷阱

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


    清單 1. 使用 JDBC 的簡單數(shù)據(jù)庫插入

    view plaincopy to clipboardprint?
    @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();  
          }  
       }  

    @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 中,這是一個(gè)不錯(cuò)的單個(gè)數(shù)據(jù)庫維護(hù)操作。但是如果需要在向數(shù)據(jù)庫插入交易訂單的同時(shí)更新帳戶余款呢?如清單 2 所示:


    清單 2. 在同一方法中執(zhí)行多次表更新

    view plaincopy to clipboardprint?
    public TradeData placeTrade(TradeData trade) throws Exception {  
       try {  
          insertTrade(trade);  
          updateAcct(trade);  
          return trade;  
       } catch (Exception up) {  
          //log the error  
          throw up;  
       }  

    public TradeData placeTrade(TradeData trade) throws Exception {
       try {
          insertTrade(trade);
          updateAcct(trade);
          return trade;
       } catch (Exception up) {
          //log the error
          throw up;
       }
    }

    在本例中,insertTrade() 和 updateAcct() 方法使用不帶事務(wù)的標(biāo)準(zhǔn) JDBC 代碼。insertTrade() 方法結(jié)束后,數(shù)據(jù)庫保存(并提交了)交易訂單。如果 updateAcct() 方法由于任意原因失敗,交易訂單仍然會(huì)在 placeTrade() 方法結(jié)束時(shí)保存在 TRADE 表內(nèi),這會(huì)導(dǎo)致數(shù)據(jù)庫出現(xiàn)不一致的數(shù)據(jù)。如果 placeTrade() 方法使用了事務(wù),這兩個(gè)活動(dòng)都會(huì)包含在一個(gè) LUW 中,如果帳戶更新失敗,交易訂單就會(huì)回滾。

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


    清單 3. 使用 JPA 的簡單插入

    view plaincopy to clipboardprint?
    public class TradingServiceImpl {  
        @PersistenceContext(unitName="trading") EntityManager em;  
     
        public long insertTrade(TradeData trade) throws Exception {  
           em.persist(trade);  
           return trade.getTradeId();  
        }  

    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() 方法來插入交易訂單。很簡單,是吧?其實(shí)不然。這段代碼不會(huì)像預(yù)期那樣向 TRADE 表插入交易訂單,也不會(huì)拋出異常。它只是返回一個(gè)值 0 作為交易訂單的鍵,而不會(huì)更改數(shù)據(jù)庫。這是事務(wù)處理的主要陷阱之一:基于 ORM 的框架需要一個(gè)事務(wù)來觸發(fā)對(duì)象緩存與數(shù)據(jù)庫之間的同步。這通過一個(gè)事務(wù)提交完成,其中會(huì)生成 SQL 代碼,數(shù)據(jù)庫會(huì)執(zhí)行需要的操作(即插入、更新、刪除)。沒有事務(wù),就不會(huì)觸發(fā) ORM 去生成 SQL 代碼和保存更改,因此只會(huì)終止方法 — 沒有異常,沒有更新。如果使用基于 ORM 的框架,就必須利用事務(wù)。您不再依賴數(shù)據(jù)庫來管理連接和提交工作。

    這些簡單的示例應(yīng)該清楚地說明,為了維護(hù)數(shù)據(jù)完整性和一致性,必須使用事務(wù)。不過對(duì)于在 Java 平臺(tái)中實(shí)現(xiàn)事務(wù)的復(fù)雜性和陷阱而言,這些示例只是涉及了冰山一角。

    Spring Framework @Transactional 注釋陷阱

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


    清單 4. 使用 @Transactional 注釋

    view plaincopy to clipboardprint?
    public class TradingServiceImpl {  
       @PersistenceContext(unitName="trading") EntityManager em;  
     
       @Transactional 
       public long insertTrade(TradeData trade) throws Exception {  
          em.persist(trade);  
          return trade.getTradeId();  
       }  

    public class TradingServiceImpl {
       @PersistenceContext(unitName="trading") EntityManager em;

       @Transactional
       public long insertTrade(TradeData trade) throws Exception {
          em.persist(trade);
          return trade.getTradeId();
       }
    }

    現(xiàn)在重新測試代碼,您發(fā)現(xiàn)上述方法仍然不能工作。問題在于您必須告訴 Spring Framework,您正在對(duì)事務(wù)管理應(yīng)用注釋。除非您進(jìn)行充分的單元測試,否則有時(shí)候很難發(fā)現(xiàn)這個(gè)陷阱。這通常只會(huì)導(dǎo)致開發(fā)人員在 Spring 配置文件中簡單地添加事務(wù)邏輯,而不會(huì)使用注釋。

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

    view plaincopy to clipboardprint?
    <tx:annotation-driven transaction-manager="transactionManager"/> 
    <tx:annotation-driven transaction-manager="transactionManager"/>

    transaction-manager 屬性保存一個(gè)對(duì)在 Spring 配置文件中定義的事務(wù)管理器 bean 的引用。這段代碼告訴 Spring 在應(yīng)用事務(wù)攔截器時(shí)使用 @Transaction 注釋。如果沒有它,就會(huì)忽略 @Transactional 注釋,導(dǎo)致代碼不會(huì)使用任何事務(wù)。

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

    @Transactional 只讀標(biāo)志陷阱

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


    清單 5. 將只讀標(biāo)志與 SUPPORTS 傳播模式結(jié)合使用 — JDBC

    view plaincopy to clipboardprint?
    @Transactional(readOnly = true, propagation=Propagation.SUPPORTS)  
    public long insertTrade(TradeData trade) throws Exception {  
       //JDBC Code...  

    @Transactional(readOnly = true, propagation=Propagation.SUPPORTS)
    public long insertTrade(TradeData trade) throws Exception {
       //JDBC Code...
    }

    當(dāng)執(zhí)行清單 5 中的 insertTrade() 方法時(shí),猜一猜會(huì)得到下面哪一種結(jié)果:

    拋出一個(gè)只讀連接異常
    正確插入交易訂單并提交數(shù)據(jù)
    什么也不做,因?yàn)閭鞑ゼ?jí)別被設(shè)置為 SUPPORTS
    是哪一個(gè)呢?正確答案是 B。交易訂單會(huì)被正確地插入到數(shù)據(jù)庫中,即使只讀標(biāo)志被設(shè)置為 true,且事務(wù)傳播模式被設(shè)置為 SUPPORTS。但這是如何做到的呢?由于傳播模式被設(shè)置為 SUPPORTS,所以不會(huì)啟動(dòng)任何事物,因此該方法有效地利用了一個(gè)本地(數(shù)據(jù)庫)事務(wù)。只讀標(biāo)志只在事務(wù)啟動(dòng)時(shí)應(yīng)用。在本例中,因?yàn)闆]有啟動(dòng)任何事務(wù),所以只讀標(biāo)志被忽略。

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


    清單 6. 將只讀標(biāo)志與 REQUIRED 傳播模式結(jié)合使用 — JDBC

    view plaincopy to clipboardprint?
    @Transactional(readOnly = true, propagation=Propagation.REQUIRED)  
    public long insertTrade(TradeData trade) throws Exception {  
       //JDBC code...  

    @Transactional(readOnly = true, propagation=Propagation.REQUIRED)
    public long insertTrade(TradeData trade) throws Exception {
       //JDBC code...
    }

    執(zhí)行清單 6 中的 insertTrade() 方法會(huì)得到下面哪一種結(jié)果呢:

    拋出一個(gè)只讀連接異常
    正確插入交易訂單并提交數(shù)據(jù)
    什么也不做,因?yàn)橹蛔x標(biāo)志被設(shè)置為 true
    根據(jù)前面的解釋,這個(gè)問題應(yīng)該很好回答。正確的答案是 A。會(huì)拋出一個(gè)異常,表示您正在試圖對(duì)一個(gè)只讀連接執(zhí)行更新。因?yàn)閱?dòng)了一個(gè)事務(wù)(REQUIRED),所以連接被設(shè)置為只讀。毫無疑問,在試圖執(zhí)行 SQL 語句時(shí),您會(huì)得到一個(gè)異常,告訴您該連接是一個(gè)只讀連接。

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

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


    清單 7. 將只讀標(biāo)志與 REQUIRED 傳播模式結(jié)合使用 — JPA

    view plaincopy to clipboardprint?
    @Transactional(readOnly = true, propagation=Propagation.REQUIRED)  
    public long insertTrade(TradeData trade) throws Exception {  
       em.persist(trade);  
       return trade.getTradeId();  

    @Transactional(readOnly = true, propagation=Propagation.REQUIRED)
    public long insertTrade(TradeData trade) throws Exception {
       em.persist(trade);
       return trade.getTradeId();
    }

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

    拋出一個(gè)只讀連接異常
    正確插入交易訂單并提交數(shù)據(jù)
    什么也不做,因?yàn)?readOnly 標(biāo)志被設(shè)置為 true
    正確的答案是 B。交易訂單會(huì)被準(zhǔn)確無誤地插入數(shù)據(jù)庫中。請(qǐng)注意,上一示例表明,在使用 REQUIRED 傳播模式時(shí),會(huì)拋出一個(gè)只讀連接異常。使用 JDBC 時(shí)是這樣。使用基于 ORM 的框架時(shí),只讀標(biāo)志只是對(duì)數(shù)據(jù)庫的一個(gè)提示,并且一條基于 ORM 框架的指令(本例中是 Hibernate)將對(duì)象緩存的 flush 模式設(shè)置為 NEVER,表示在這個(gè)工作單元中,該對(duì)象緩存不應(yīng)與數(shù)據(jù)庫同步。不過,REQUIRED 傳播模式會(huì)覆蓋所有這些內(nèi)容,允許事務(wù)啟動(dòng)并工作,就好像沒有設(shè)置只讀標(biāo)志一樣。

    這令我想到了另一個(gè)我經(jīng)常碰到的主要陷阱。閱讀了前面的所有內(nèi)容后,您認(rèn)為如果只對(duì) @Transactional 注釋設(shè)置只讀標(biāo)志,清單 8 中的代碼會(huì)得到什么結(jié)果呢?


    清單 8. 使用只讀標(biāo)志 — JPA

    view plaincopy to clipboardprint?
    @Transactional(readOnly = true)  
    public TradeData getTrade(long tradeId) throws Exception {  
       return em.find(TradeData.class, tradeId);  

    @Transactional(readOnly = true)
    public TradeData getTrade(long tradeId) throws Exception {
       return em.find(TradeData.class, tradeId);
    }

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

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


    清單 9. 使用只讀標(biāo)志和 SUPPORTS 傳播模式進(jìn)行選擇操作

    view plaincopy to clipboardprint?
    @Transactional(readOnly = true, propagation=Propagation.SUPPORTS)  
    public TradeData getTrade(long tradeId) throws Exception {  
       return em.find(TradeData.class, tradeId);  

    @Transactional(readOnly = true, propagation=Propagation.SUPPORTS)
    public TradeData getTrade(long tradeId) throws Exception {
       return em.find(TradeData.class, tradeId);
    }

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

    清單 10. 刪除 @Transactional 注釋進(jìn)行選擇操作

    view plaincopy to clipboardprint?
    public TradeData getTrade(long tradeId) throws Exception {  
       return em.find(TradeData.class, tradeId);  

    public TradeData getTrade(long tradeId) throws Exception {
       return em.find(TradeData.class, tradeId);
    }

    REQUIRES_NEW 事務(wù)屬性陷阱

    不管是使用 Spring Framework,還是使用 EJB,使用 REQUIRES_NEW 事務(wù)屬性都會(huì)得到不好的結(jié)果并導(dǎo)致數(shù)據(jù)損壞和不一致。REQUIRES_NEW 事務(wù)屬性總是會(huì)在啟動(dòng)方法時(shí)啟動(dòng)一個(gè)新的事務(wù)。許多開發(fā)人員都錯(cuò)誤地使用 REQUIRES_NEW 屬性,認(rèn)為它是確保事務(wù)啟動(dòng)的正確方法。考慮清單 11 中的兩個(gè)方法:


    清單 11. 使用 REQUIRES_NEW 事務(wù)屬性

    view plaincopy to clipboardprint?
    @Transactional(propagation=Propagation.REQUIRES_NEW)  
    public long insertTrade(TradeData trade) throws Exception {...}  
     
    @Transactional(propagation=Propagation.REQUIRES_NEW)  
    public void updateAcct(TradeData trade) throws Exception {...} 
    @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 中的兩個(gè)方法都是公共方法,這意味著它們可以單獨(dú)調(diào)用。當(dāng)使用 REQUIRES_NEW 屬性的幾個(gè)方法通過服務(wù)間通信或編排在同一邏輯工作單元內(nèi)調(diào)用時(shí),該屬性就會(huì)出現(xiàn)問題。例如,假設(shè)在清單 11 中,您可以獨(dú)立于一些用例中的任何其他方法來調(diào)用 updateAcct() 方法,但也有在 insertTrade() 方法中調(diào)用 updateAcct() 方法的情況。現(xiàn)在如果調(diào)用 updateAcct() 方法后拋出異常,交易訂單就會(huì)回滾,但帳戶更新將會(huì)提交給數(shù)據(jù)庫,如清單 12 所示:


    清單 12. 使用 REQUIRES_NEW 事務(wù)屬性的多次更新

    view plaincopy to clipboardprint?
    @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!  
       ...  

    @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!
       ...
    }

    之所以會(huì)發(fā)生這種情況是因?yàn)?updateAcct() 方法中啟動(dòng)了一個(gè)新事務(wù),所以在 updateAcct() 方法結(jié)束后,事務(wù)將被提交。使用 REQUIRES_NEW 事務(wù)屬性時(shí),如果存在現(xiàn)有事務(wù)上下文,當(dāng)前的事務(wù)會(huì)被掛起并啟動(dòng)一個(gè)新事務(wù)。方法結(jié)束后,新的事務(wù)被提交,原來的事務(wù)繼續(xù)執(zhí)行。

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

    事務(wù)回滾陷阱

    我將最常見的事務(wù)陷阱留到最后來講。遺憾的是,我在生產(chǎn)代碼中多次遇到這個(gè)錯(cuò)誤。我首先從 Spring Framework 開始,然后介紹 EJB 3。

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


    清單 13. 沒有回滾支持

    view plaincopy to clipboardprint?
    @Transactional(propagation=Propagation.REQUIRED)  
    public TradeData placeTrade(TradeData trade) throws Exception {  
       try {  
          insertTrade(trade);  
          updateAcct(trade);  
          return trade;  
       } catch (Exception up) {  
          //log the error  
          throw up;  
       }  

    @Transactional(propagation=Propagation.REQUIRED)
    public TradeData placeTrade(TradeData trade) throws Exception {
       try {
          insertTrade(trade);
          updateAcct(trade);
          return trade;
       } catch (Exception up) {
          //log the error
          throw up;
       }
    }

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

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

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

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


    清單 14. 添加事務(wù)回滾支持 — Spring

    view plaincopy to clipboardprint?
    @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 error  
          throw up;  
       }  

    @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 error
          throw up;
       }
    }

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

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


    清單 15. 添加事務(wù)回滾支持 — EJB

    view plaincopy to clipboardprint?
    @TransactionAttribute(TransactionAttributeType.REQUIRED)  
    public TradeData placeTrade(TradeData trade) throws Exception {  
       try {  
          insertTrade(trade);  
          updateAcct(trade);  
          return trade;  
       } catch (Exception up) {  
          //log the error  
          sessionCtx.setRollbackOnly();  
          throw up;  
       }  

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public TradeData placeTrade(TradeData trade) throws Exception {
       try {
          insertTrade(trade);
          updateAcct(trade);
          return trade;
       } catch (Exception up) {
          //log the error
          sessionCtx.setRollbackOnly();
          throw up;
       }
    }

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

    結(jié)束語

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

    參考資料

    學(xué)習(xí)

    您可以參閱本文在 developerWorks 全球網(wǎng)站上的 英文原文。
    Straight Through Processing for Financial Service Firms (Hal McIntyre,Summit Group 出版社,2004):了解在金融服務(wù)事務(wù)處理中,更多有關(guān)造成壞數(shù)據(jù)的原因及其成本的信息。
    Java Transaction Design Strategies (Mark Richards,C4Media 出版,2006):本書深入討論了 Java 平臺(tái)中的事務(wù)。
    Java Transaction Processing (Mark Little,Prentice Hall,2004):本書是另一本比較好的關(guān)于事務(wù)的參考書。
    第 9 章. 事務(wù)管理:在 Spring Framework 2.5 文檔的這部分中,可以找到更多有關(guān) Spring 事務(wù)處理的信息。
    Enterprise JavaBeans 3.0 資源站點(diǎn):在此可以找到有關(guān) EJB 3.0 規(guī)范的文檔。
    瀏覽 技術(shù)書店,查閱有關(guān)本文所述主題及其他技術(shù)主題的圖書。
    developerWorks Java 技術(shù)專區(qū):提供了幾百篇有關(guān) Java 編程的各個(gè)方面的文章。
    本文出處:http://www.ibm.com/developerworks/cn/java/j-ts1.html

     

    主站蜘蛛池模板: 免费的一级片网站| 亚洲av无码成人精品区一本二本 | 成年人在线免费看视频| 两个人看www免费视频| 国产亚洲视频在线播放大全| 亚洲国产理论片在线播放| 亚洲国产精品一区二区久久hs| 免费a级毛片网站| 最新中文字幕免费视频| 97热久久免费频精品99 | 久久久久亚洲AV片无码下载蜜桃| 中文字幕精品亚洲无线码一区| 国产精品久久免费视频| 女人被弄到高潮的免费视频 | 亚洲电影免费在线观看| 国产亚洲精品影视在线产品| 免费大片黄手机在线观看| 日本久久久免费高清| 日韩免费a级毛片无码a∨| 5g影院5g天天爽永久免费影院| 日韩成人免费视频| 三根一起会坏掉的好痛免费三级全黄的视频在线观看| 色婷婷六月亚洲综合香蕉| 亚洲欧美自偷自拍另类视| 亚洲精品第一综合99久久| 亚洲国产成人久久综合一区| 亚洲精品国产第1页| 亚洲网站在线播放| 亚洲高清日韩精品第一区| 久久久亚洲欧洲日产国码二区| 无码乱人伦一区二区亚洲| 无码专区—VA亚洲V天堂| 亚洲一区二区三区四区在线观看 | a级黄色毛片免费播放视频| 91成人免费观看在线观看| a毛片视频免费观看影院| 嫩草影院在线播放www免费观看| a毛片全部免费播放| 精品无码无人网站免费视频| 99久久免费国产香蕉麻豆| 成人免费毛片视频|