Spring事務傳播屬性和隔離級別

一、Spring事務的隔離級別


 1. ISOLATION_DEFAULT: 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別.
      另外四個與JDBC的隔離級別相對應
 2. ISOLATION_READ_UNCOMMITTED: 這是事務最低的隔離級別,它充許令外一個事務可以看到這個事務未提交的數據。
      這種隔離級別會產生髒讀,不可重複讀和幻像讀。
 3. ISOLATION_READ_COMMITTED: 保證一個事務修改的數據提交後才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據
 4. ISOLATION_REPEATABLE_READ: 這種事務隔離級別可以防止髒讀,不可重複讀。但是可能出現幻像讀。
      它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下面的情況產生(不可重複讀)。
 5. ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理爲順序執行。
      除了防止髒讀,不可重複讀外,還避免了幻像讀。

什麼是髒數據,髒讀,不可重複讀,幻覺讀?
 髒讀: 指當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,
     另外一個事務也訪問這個數據,然後使用了這個數據。因爲這個數據是還沒有提交的數據, 那麼另外一
     個事務讀到的這個數據是髒數據,依據髒數據所做的操作可能是不正確的。
   
 不可重複讀: 指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。
             那麼,在第一個事務中的兩次讀數據之間,由於第二個事務的修改,那麼第一個事務兩次讀到的數據
             可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱爲是不可重複讀。
           
 幻覺讀: 指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及
         到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,
         以後就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。


二、Spring事務的傳播屬性


在TransactionDefinition接口中定義了七個事務傳播行爲。

PROPAGATION_REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啓一個新的事務。

PROPAGATION_SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。

PROPAGATION_MANDATORY 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。

PROPAGATION_REQUIRES_NEW 總是開啓一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。

PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。

PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常

PROPAGATION_NESTED如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行

在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),這樣就做成一個只讀事務,可以提高效率。

       各種屬性的意義:

       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(事務隔離等級)

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://blog.csdn.net/hijiankang/article/details/9174461


其他參考文檔:

http://blog.csdn.net/it_man/article/details/5074371

http://www.cnblogs.com/yangy608/archive/2011/06/29/2093478.html

http://www.open-open.com/lib/view/open1350865116821.html

http://www.cnblogs.com/rushoooooo/archive/2011/08/28/2155960.html (Spring聲明式事務配置管理方法)

http://www.blogjava.net/robbie/archive/2009/04/05/264003.html (Spring事務配置的五種方式)

MYSQL事務隔離級別詳解

http://xm-king.iteye.com/blog/770721



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章