1. 程式人生 > >Spring 註解@Transactional詳解

Spring 註解@Transactional詳解


在service類前加上@Transactional,宣告這個service所有方法需要事務管理。每一個業務方法開始時都會開啟一個事務。

Spring預設情況下會對執行期例外(RunTimeException)進行事務回滾。這個例外是unchecked如果遇到checked意外就不回滾。


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

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

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

  在整個方法執行前就不會開啟事務。 還可以寫成:           @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),這樣就做成一個只讀事務,可以提高效率。 


種屬性的意

//事務傳播屬性
@Transactional(propagation=Propagation.REQUIRED)//如果有事務,那麼加入事務,沒有的話新建立一個


@Transactional(propagation=Propagation.NOT_SUPPORTED)//這個方法不開啟事務
@Transactional(propagation=Propagation.REQUIREDS_NEW)//不管是否存在事務,都建立一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
@Transactional(propagation=Propagation.MANDATORY)//必須在一個已有的事務中執行,否則丟擲異常
@Transactional(propagation=Propagation.NEVER)//不能在一個事務中執行,就是當前必須沒有事務,否則丟擲異常
@Transactional(propagation=Propagation.SUPPORTS)//其他bean呼叫這個方法,如果在其他bean中聲明瞭事務,就是用事務。沒有宣告,就不用事務。

@Transactional(propagation=Propagation.NESTED)//如果一個活動的事務存在,則執行在一個巢狀的事務中,如果沒有活動的事務,則按照REQUIRED屬性執行,它使用一個單獨的事務。這個書屋擁有多個回滾的儲存點,內部事務的回滾不會對外部事務造成影響,它只對DataSource TransactionManager事務管理器起效。
@Transactional(propagation=Propagation.REQUIRED,readOnly=true)//只讀,不能更新,刪除
@Transactional(propagation=Propagation.REQUIRED,timeout=30)//超時30秒

@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)//資料庫隔離級別

事務傳播行為種類

Spring在TransactionDefinition介面中規定了7種類型的事務傳播行為,

它們規定了事務方法和事務方法發生巢狀呼叫時事務如何進行傳播:

說明

PROPAGATION_REQUIRED

如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。

PROPAGATION_SUPPORTS

支援當前事務,如果當前沒有事務,就以非事務方式執行。

PROPAGATION_MANDATORY

使用當前的事務,如果當前沒有事務,就丟擲異常。

PROPAGATION_REQUIRES_NEW

新建事務,如果當前存在事務,把當前事務掛起。

PROPAGATION_NOT_SUPPORTED

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

PROPAGATION_NEVER

以非事務方式執行,如果當前存在事務,則丟擲異常。

PROPAGATION_NESTED

如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。



事務陷阱-1

清單 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 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 的簡單插入

<span style="color:#330033;">public class TradingServiceImpl {   
    @PersistenceContext(unitName="trading") EntityManager em;   

    public long insertTrade(TradeData trade) throws Exception {   
       em.persist(trade);   
       return trade.getTradeId();   
    }   
} </span>
注意,清單 3 在 EntityManager 上呼叫了 persist() 方法來插入交易訂單。很簡單,是吧?其實不然。這段程式碼不會像預期那樣向 TRADE 表插入交易訂單,也不會丟擲異常。它只是返回一個值 0 作為交易訂單的鍵,而不會更改資料庫。這是事務處理的主要陷阱之一:基於 ORM 的框架需要一個事務來觸發物件快取與資料庫之間的同步。這通過一個事務提交完成,其中會生成 SQL 程式碼,資料庫會執行需要的操作(即插入、更新、刪除)。沒有事務,就不會觸發 ORM 去生成 SQL 程式碼和儲存更改,因此只會終止方法 — 沒有異常,沒有更新。如果使用基於 ORM 的框架,就必須利用事務。您不再依賴資料庫來管理連線和提交工作。

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

Spring Framework @Transactional 註釋陷阱-3

清單 4. 使用 @Transactional 註釋
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 傳播
<span style="color:#330033;">@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)   
public long insertTrade(TradeData trade) throws Exception {   
   //JDBC Code...   
} </span>
當執行清單 5 中的 insertTrade() 方法時,猜一猜會得到下面哪一種結果:
丟擲一個只讀連線異常 
正確插入交易訂單並提交資料 
什麼也不做,因為傳播級別被設定為 SUPPORTS 
是哪一個呢?正確答案是 B。交易訂單會被正確地插入到資料庫中,即使只讀標誌被設定為 true,且事務傳播模式被設定為 SUPPORTS。但這是如何做到的呢?由於傳播模式被設定為 SUPPORTS,所以不會啟動任何事物,因此該方法有效地利用了一個本地(資料庫)事務。只讀標誌只在事務啟動時應用。在本例中,因為沒有啟動任何事務,所以只讀標誌被忽略。

Spring Framework @Transactional 註釋陷阱-4

清單 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

<span style="color:#330033;">@Transactional(readOnly = true, propagation=Propagation.REQUIRED)   
public long insertTrade(TradeData trade) throws Exception {   
   em.persist(trade);   
   return trade.getTradeId();   
} </span>
清單 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 註釋進行選擇操作

<span style="font-size:14px;">public TradeData getTrade(long tradeId) throws Exception {    </span>
<span style="font-size:14px;">   return em.find(TradeData.class, tradeId);   
} </span>

REQUIRES_NEW 事務屬性陷阱

不管是使用 Spring Framework,還是使用 EJB,使用 REQUIRES_NEW 事務屬性都會得到不好的結果並導致資料損壞和不一致。REQUIRES_NEW 事務屬性總是會在啟動方法時啟動一個新的事務。許多開發人員都錯誤地使用 REQUIRES_NEW 屬性,認為它是確保事務啟動的正確方法。

Spring Framework @Transactional 註釋陷阱-5

清單 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 事務屬性的多次更新

<span style="font-size:14px;">@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!   
   ...   
} </span>

之所以會發生這種情況是因為 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 error   
      throw up;   
   }   
} 

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

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

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

Spring Framework @Transactional 註釋陷阱-6

使用 Declarative 事務模式時,必須指定容器或框架應該如何處理受控異常。在 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 error   
      throw up;   
   }   
} 
 

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

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


清單 15. 新增事務回滾支援 — EJB 

<span style="font-size:14px;">@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;   
   }   
} </span>

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

Isolation Level(事務隔離等級)

1、Serializable:最嚴格的級別,事務序列執行,資源消耗最大;
2、REPEATABLE READ:保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的資料。避免了“髒讀取”和“不可重複讀取”的情況,但是帶來了更多的效能損失。
3、READ COMMITTED:大多數主流資料庫的預設事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的資料,避免了“髒讀取”。該級別適用於大多數系統。
4、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標誌表示對應的事務應該被最優化為只讀事務。


原文地址:http://www.th7.cn/Program/java/201208/88393.shtml