Spring @Transactional

Spring事務的傳播行爲 java

 

在service類前加上@Transactional,聲明這個service全部方法須要事務管理。每個業務方法開始時都會打開一個事務。 spring

Spring默認狀況下會對運行期例外(RunTimeException)進行事務回滾。這個例外是unchecked sql

若是遇到checked意外就不回滾。 數據庫

如何改變默認規則: 緩存

1 讓checked例外也回滾:在整個方法前加上 @Transactional(rollbackFor=Exception.class) 服務器

2 讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class) session

3 不須要事務管理的(只查詢的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED) 併發

 

注意: 若是異常被try{}catch{}了,事務就不回滾了,若是想讓事務回滾必須再往外拋try{}catch{throw Exception}。 app

 

spring——@Transactional事務無論理jdbc,因此要本身把jdbc事務回滾。 框架

下面給出了回滾JDBC事務的代碼示例:

Java代碼
  1. public void processT(String orders) {   
  2. Context initCtx = new InitialContext();   
  3. javax.sql.DataSource ds = javax.sql.DataSource)initCtx.lookup   
  4. (「java:comp/env/jdbc/OrdersDB」);   
  5. java.sql.Connection conn = ds.getConnection();   
  6. try {   
  7. conn.setAutoCommit( false ); //更改JDBC事務的默認提交方式   
  8. orderNo = createOrder( orders );   
  9. updateOrderStatus(orderNo, 「orders created」);   
  10. conn.commit(); //提交JDBC事務   
  11. } catch ( Exception e ){   
  12. try {   
  13. conn.rollback(); //回滾sJDBC事務   
  14. throw new EJBException(「事務回滾: 「 + e.getMessage());   
  15. } catch ( SQLException sqle ){   
  16. throw new EJBException(「出現SQL操做錯誤: 「 + sqle.getMessage());   
  17. }   
  18. }   
  19. }  
[java]  view plain copy
  1. public void processT(String orders) {  
  2. Context initCtx = new InitialContext();  
  3. javax.sql.DataSource ds = javax.sql.DataSource)initCtx.lookup  
  4. (「java:comp/env/jdbc/OrdersDB」);  
  5. java.sql.Connection conn = ds.getConnection();  
  6. try{  
  7. conn.setAutoCommit( false ); //更改JDBC事務的默認提交方式  
  8. orderNo = createOrder( orders );  
  9. updateOrderStatus(orderNo, 「orders created」);  
  10. conn.commit();//提交JDBC事務  
  11. }catch( Exception e ){  
  12. try{  
  13. conn.rollback();//回滾sJDBC事務  
  14. throw new EJBException(「事務回滾: 「 + e.getMessage());  
  15. }catch( SQLException sqle ){  
  16. throw new EJBException(「出現SQL操做錯誤: 「 + sqle.getMessage());  
  17. }  
  18. }  
  19. }  

 

下面給出了JTA事務代碼示例:

Java代碼
  1. public void processOrder(String orderMessage) {   
  2. UserTransaction transaction = mySessionContext.getUserTransaction(); //得到JTA事務   
  3. try {   
  4. transaction.begin(); //開始JTA事務   
  5. orderNo = sendOrder(orderMessage);   
  6. updateOrderStatus(orderNo, 「order sent」);   
  7. transaction.commit(); //提交JTA事務   
  8. } catch (Exception e){   
  9. try {   
  10. transaction.rollback(); //回滾JTA事務   
  11. } catch (SystemException se){   
  12. se.printStackTrace();   
  13. }   
  14. throw new EJBException(「事務回滾: 「 + e.getMessage());   
  15. }   
  16. }  

 

 

在整個方法運行前就不會開啓事務

       還能夠加上:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),這樣就作成一個只讀事務,能夠提升效率。

       各類屬性的意義:

       REQUIRED:業務方法須要在一個容器裏運行。若是方法運行時,已經處在一個事務中,那麼加入到這個事務,不然本身新建一個新的事務。

       NOT_SUPPORTED:聲明方法不須要事務。若是方法沒有關聯到一個事務,容器不會爲他開啓事務,若是方法在一個事務中被調用,該事務會被掛起,調用結束後,原先的事務會恢復執行。

       REQUIRESNEW:無論是否存在事務,該方法總彙爲本身發起一個新的事務。若是方法已經運行在一個事務中,則原有事務掛起,新的事務被建立。

       MANDATORY:該方法只能在一個已經存在的事務中執行,業務方法不能發起本身的事務。若是在沒有事務的環境下被調用,容器拋出例外。

       SUPPORTS:該方法在某個事務範圍內被調用,則方法成爲該事務的一部分。若是方法在該事務範圍外被調用,該方法就在沒有事務的環境下執行。

       NEVER:該方法絕對不能在事務範圍內執行。若是在就拋例外。只有該方法沒有關聯到任何事務,才正常執行。

       NESTED:若是一個活動的事務存在,則運行在一個嵌套的事務中。若是沒有活動事務,則按REQUIRED屬性執行。它使用了一個單獨的事務,這個事務 擁有多個能夠回滾的保存點。內部事務的回滾不會對外部事務形成影響。它只對DataSourceTransactionManager事務管理器起效。

 

 

事務陷阱-1

 

清單 1. 使用 JDBC 的簡單數據庫插入

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 代碼沒有包含任何事務邏輯,它只是在數據庫中保存 TRADE 表中的交易訂單。在本例中,數據庫處理事務邏輯。

在 LUW 中,這是一個不錯的單個數據庫維護操做。可是若是須要在向數據庫插入交易訂單的同時更新賬戶餘款呢?如清單 2 所示:


清單 2. 在同一方法中執行屢次表更新

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() 方法使用不帶事務的標準 JDBC 代碼。insertTrade() 方法結束後,數據庫保存(並提交了)交易訂單。若是 updateAcct() 方法因爲任意緣由失敗,交易訂單仍然會在 placeTrade() 方法結束時保存在 TRADE 表內,這會致使數據庫出現不一致的數據。若是 placeTrade() 方法使用了事務,這兩個活動都會包含在一個 LUW 中,若是賬戶更新失敗,交易訂單就會回滾。

 

 

事務陷阱-2

 

隨着 Java 持久性框架的不斷普及,如 Hibernate、TopLink 和 Java 持久性 API(Java Persistence API,JPA),咱們不多再會去編寫簡單的 JDBC 代碼。更常見的狀況是,咱們使用更新的對象關係映射(ORM)框架來減輕工做,即用幾個簡單的方法調用替換全部麻煩的 JDBC 代碼。例如,要插入 清單 1 中 JDBC 代碼示例的交易訂單,使用帶有 JPA 的 Spring Framework,就能夠將 TradeData 對象映射到 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 上調用了 persist() 方法來插入交易訂單。很簡單,是吧?其實否則。這段代碼不會像預期那樣向 TRADE 表插入交易訂單,也不會拋出異常。它只是返回一個值 0 做爲交易訂單的鍵,而不會更改數據庫。這是事務處理的主要陷阱之一:基於 ORM 的框架須要一個事務來觸發對象緩存與數據庫之間的同步。這經過一個事務提交完成,其中會生成 SQL 代碼,數據庫會執行須要的操做(即插入、更新、刪除)。沒有事務,就不會觸發 ORM 去生成 SQL 代碼和保存更改,所以只會終止方法 — 沒有異常,沒有更新。若是使用基於 ORM 的框架,就必須利用事務。您再也不依賴數據庫來管理鏈接和提交工做。

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

 

 

 

Spring Framework @Transactional 註釋陷阱-3

 

 

清單 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();
   }
}

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

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

view plaincopy to clipboardprint?
<tx:annotation-driven transaction-manager="transactionManager"/> 
<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

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

當執行清單 5 中的 insertTrade() 方法時,猜一猜會獲得下面哪種結果:
拋出一個只讀鏈接異常 
正確插入交易訂單並提交數據 
什麼也不作,由於傳播級別被設置爲 SUPPORTS 
是哪個呢?正確答案是 B。交易訂單會被正確地插入到數據庫中,即便只讀標誌被設置爲 true,且事務傳播模式被設置爲 SUPPORTS。但這是如何作到的呢?因爲傳播模式被設置爲 SUPPORTS,因此不會啓動任何事物,所以該方法有效地利用了一個本地(數據庫)事務。只讀標誌只在事務啓動時應用。在本例中,由於沒有啓動任何事 務,因此只讀標誌被忽略。

 

 

Spring Framework @Transactional 註釋陷阱-4

 

清單 6 中的 @Transactional 註釋在設置了只讀標誌且傳播模式被設置爲 REQUIRED 時,它的做用是什麼呢?


清單 6. 將只讀標誌與 REQUIRED 傳播模式結合使用 — 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...
}

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

拋出一個只讀鏈接異常 
正確插入交易訂單並提交數據 
什麼也不作,由於只讀標誌被設置爲 true 
根據前面的解釋,這個問題應該很好回答。正確的答案是 A。會拋出一個異常,表示您正在試圖對一個只讀鏈接執行更新。由於啓動了一個事務(REQUIRED),因此鏈接被設置爲只讀。毫無疑問,在試圖執行 SQL 語句時,您會獲得一個異常,告訴您該鏈接是一個只讀鏈接。

關於只讀標誌很奇怪的一點是:要使用它,必須啓動一個事務。若是隻是讀取數據,須要事務嗎?答案是根本不須要。啓動一個事務來執行只讀操做會增長處 理線程的開銷,並會致使數據庫發生共享讀取鎖定(具體取決於使用的數據庫類型和設置的隔離級別)。總的來講,在獲取基於 JDBC 的 Java 持久性時,使用只讀標誌有點毫無心義,並會啓動沒必要要的事務而增長額外的開銷。

使用基於 ORM 的框架會怎樣呢?按照上面的測試,若是在結合使用 JPA 和 Hibernate 時調用 insertTrade() 方法,清單 7 中的 @Transactional 註釋會獲得什麼結果?


清單 7. 將只讀標誌與 REQUIRED 傳播模式結合使用 — 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() 方法會獲得下面哪種結果:

拋出一個只讀鏈接異常 
正確插入交易訂單並提交數據 
什麼也不作,由於 readOnly 標誌被設置爲 true 
正確的答案是 B。交易訂單會被準確無誤地插入數據庫中。請注意,上一示例代表,在使用 REQUIRED 傳播模式時,會拋出一個只讀鏈接異常。使用 JDBC 時是這樣。使用基於 ORM 的框架時,只讀標誌只是對數據庫的一個提示,而且一條基於 ORM 框架的指令(本例中是 Hibernate)將對象緩存的 flush 模式設置爲 NEVER,表示在這個工做單元中,該對象緩存不該與數據庫同步。不過,REQUIRED 傳播模式會覆蓋全部這些內容,容許事務啓動並工做,就好像沒有設置只讀標誌同樣。

這令我想到了另外一個我常常碰到的主要陷阱。閱讀了前面的全部內容後,您認爲若是隻對 @Transactional 註釋設置只讀標誌,清單 8 中的代碼會獲得什麼結果呢?


清單 8. 使用只讀標誌 — 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() 方法會執行如下哪種操做?

啓動一個事務,獲取交易訂單,而後提交事務 
獲取交易訂單,但不啓動事務 
正確的答案是 A。一個事務會被啓動並提交。不要忘了,@Transactional 註釋的默認傳播模式是 REQUIRED。這意味着事務會在沒必要要的狀況下啓動。根據使用的數據庫,這會引發沒必要要的共享鎖,可能會使數據庫中出現死鎖的狀況。此外,啓動和中止 事務將消耗沒必要要的處理時間和資源。總的來講,在使用基於 ORM 的框架時,只讀標誌基本上毫無用處,在大多數狀況下會被忽略。但若是您堅持使用它,請記得將傳播模式設置爲 SUPPORTS(如清單 9 所示),這樣就不會啓動事務:
清單 9. 使用只讀標誌和 SUPPORTS 傳播模式進行選擇操做

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);
}

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

清單 10. 刪除 @Transactional 註釋進行選擇操做

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 事務屬性陷阱

無論是使用 Spring Framework,仍是使用 EJB,使用 REQUIRES_NEW 事務屬性都會獲得很差的結果並致使數據損壞和不一致。REQUIRES_NEW 事務屬性老是會在啓動方法時啓動一個新的事務。許多開發人員都錯誤地使用 REQUIRES_NEW 屬性,認爲它是確保事務啓動的正確方法。

 

 

 

 

 

Spring Framework @Transactional 註釋陷阱-5

 

 

清單 11. 使用 REQUIRES_NEW 事務屬性

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 中的兩個方法都是公共方法,這意味着它們能夠單獨調用。當使用 REQUIRES_NEW 屬性的幾個方法經過服務間通訊或編排在同一邏輯工做單元內調用時,該屬性就會出現問題。例如,假設在清單 11 中,您能夠獨立於一些用例中的任何其餘方法來調用 updateAcct() 方法,但也有在 insertTrade() 方法中調用 updateAcct() 方法的狀況。如今若是調用 updateAcct() 方法後拋出異常,交易訂單就會回滾,但賬戶更新將會提交給數據庫,如清單 12 所示:


清單 12. 使用 REQUIRES_NEW 事務屬性的屢次更新

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

之因此會發生這種狀況是由於 updateAcct() 方法中啓動了一個新事務,因此在 updateAcct() 方法結束後,事務將被提交。使用 REQUIRES_NEW 事務屬性時,若是存在現有事務上下文,當前的事務會被掛起並啓動一個新事務。方法結束後,新的事務被提交,原來的事務繼續執行。

因爲這種行爲,只有在被調用方法中的數據庫操做須要保存到數據庫中,而無論覆蓋事務的結果如什麼時候,才應該使用 REQUIRES_NEW 事務屬性。好比,假設嘗試的全部股票交易都必須被記錄在一個審計數據庫中。出於驗證錯誤、資金不足或其餘緣由,無論交易是否失敗,這條信息都須要被持久 化。若是沒有對審計方法使用 REQUIRES_NEW 屬性,審計記錄就會連同嘗試執行的交易一塊兒回滾。使用 REQUIRES_NEW 屬性能夠確保無論初始事務的結果如何,審計數據都會被保存。這裏要注意的一點是,要始終使用 MANDATORY 或 REQUIRED 屬性,而不是 REQUIRES_NEW,除非您有足夠的理由來使用它,相似審計示例中的那些理由。

事務回滾陷阱

我將最多見的事務陷阱留到最後來說。遺憾的是,我在生產代碼中屢次遇到這個錯誤。我首先從 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;
   }
}
假設賬戶中沒有足夠的資金來購買須要的股票,或者尚未準備購買或出售股票,並拋出了一個受控異常(例如 FundsNotAvailableException),那麼交易訂單會保存在數據庫中嗎?仍是整個邏輯工做單元將執行回滾?答案出乎意料:根據受控異 常(無論是在 Spring Framework 中仍是在 EJB 中),事務會提交它還未提交的全部工做。使用清單 13,這意味着,若是在執行 updateAcct() 方法期間拋出受控異常,就會保存交易訂單,但不會更新賬戶來反映交易狀況。

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

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

 

 

 

 

 

Spring Framework @Transactional 註釋陷阱-6

 

使用 Declarative 事務模式時,必須指定容器或框架應該如何處理受控異常。在 Spring Framework 中,經過 @Transactional 註釋中的 rollbackFor 參數進行指定,如清單 14 所示:


清單 14. 添加事務回滾支持 — 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 參數。這個參數接受一個單一異常類或一組異常類,您也可使用 rollbackForClassName 參數將異常的名稱指定爲 Java String 類型。還可使用此屬性的相反形式(noRollbackFor)指定除某些異常之外的全部異常應該強制回滾。一般大多數開發人員指定 Exception.class 做爲值,表示該方法中的全部異常應該強制回滾。

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


清單 15. 添加事務回滾支持 — 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;
   }
}
調用 setRollbackOnly() 方法後,就不能改變主意了;唯一可能的結果是在啓動事務的方法完成後回滾事務。本系列後續文章中描述的事務策略將介紹什麼時候、何處使用回滾指令,以及什麼時候使用 REQUIRED 與 MANDATORY 事務屬性。

 

 

 

 

Isolation Level(事務隔離等級)

 

一、Serializable:最嚴格的級別,事務串行執行,資源消耗最大;
二、REPEATABLE READ:保證了一個事務不會修改已經由另外一個事務讀取但未提交(回滾)的數據。避免了「髒讀取」和「不可重複讀取」的狀況,可是帶來了更多的性能損失。
三、READ COMMITTED:大多數主流數據庫的默認事務等級,保證了一個事務不會讀到另外一個並行事務已修改但未提交的數據,避免了「髒讀取」。該級別適用於大多數系統。
四、Read Uncommitted:保證了讀取過程當中不會讀取到非法數據。隔離級別在於處理多事務的併發問題。
咱們知道並行能夠提升數據庫的吞吐量和效率,可是並非全部的併發事務均可以併發運行。
咱們首先說併發中可能發生的3中不討人喜歡的事情
1: Dirty reads--讀髒數據。也就是說,好比事務A的未提交(還依然緩存)的數據被事務B讀走,若是事務A失敗回滾,會致使事務B所讀取的的數據是錯誤的。
2: non-repeatable reads--數據不可重複讀。好比事務A中兩處讀取數據-total-的值。在第一讀的時候,total是100,而後事務B就把total的數據改爲 200,事務A再讀一次,結果就發現,total居然就變成200了,形成事務A數據混亂。
3: phantom reads--幻象讀數據,這個和non-repeatable reads類似,也是同一個事務中屢次讀不一致的問題。可是non-repeatable reads的不一致是由於他所要取的數據集被改變了(好比total的數據),可是phantom reads所要讀的數據的不一致卻不是他所要讀的數據集改變,而是他的條件數據集改變。好比Select account.id where account.name="ppgogo*",第一次讀去了6個符合條件的id,第二次讀取的時候,因爲事務b把一個賬號的名字由"dd"改 成"ppgogo1",結果取出來了7個數據。

  Dirty reads non-repeatable reads phantom reads
Serializable 不會 不會 不會
REPEATABLE READ 不會 不會
READ COMMITTED 不會
Read Uncommitted

readOnly 事務屬性中的readOnly標誌表示對應的事務應該被最優化爲只讀事務。
相關文章
相關標籤/搜索