spring中@Transactional的各個引數的意思和常見的事務陷阱 spring中@Transactional的各個引數的意思和常見的事務陷阱
在service類前加上@Transactional,宣告這個service所有方法需要事務管理。每一個業務方法開始時都會開啟一個事務。
Spring預設情況下會對執行期例外(RunTimeException)進行事務回滾。這個例外是unchecked
如果遇到checked意外就不回滾。
如何改變預設規則:
1 讓checked" />
<link href="https://csdnimg.cn/public/favicon.ico" rel="SHORTCUT ICON"> <title>spring中@Transactional的各個引數的意思和常見的事務陷阱 - zxl0016的專欄 - CSDN部落格</title> <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/detail-e2f32ea0d6.min.css"> <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/themes/skin3-template/skin3-template-9b39979775.min.css"> <script type="text/javascript"> var username = "zxl0016"; var blog_address = "https://blog.csdn.net/zxl0016"; var static_host = "https://csdnimg.cn/release/phoenix/"; var currentUserName = "ajiu_9999"; var isShowAds = true; var isOwner = false; var loginUrl = "https://passport.csdn.net/account/login?from=https://blog.csdn.net/zxl0016/article/details/7701464" var blogUrl = "https://blog.csdn.net/"; //頁面面板樣式 var curSkin = "skin3-template"; // 第四正規化所需資料 var articleTitles = "spring中@Transactional的各個引數的意思和常見的事務陷阱 - zxl0016的專欄"; var articleID = "7701464"; var nickName = "zxl0016"; </script> <script type="text/javascript"> // Traffic Stats of the entire Web site By baidu var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?6bcd52f51e9b3dce32bec4a3997715ac"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); // Traffic Stats of the entire Web site By baidu end </script> <script src="https://csdnimg.cn/public/common/libs/jquery/jquery-1.9.1.min.js" type="text/javascript"></script> <script src="https://csdnimg.cn/rabbit/exposure-click/main-1.0.6.js"></script> <!-- 新版上報 --> <script src="//g.csdnimg.cn/track/1.1.1/track.js" type="text/javascript"></script> <!-- 新版上報end --> <link rel="stylesheet" href="https://csdnimg.cn/public/sandalstrap/1.4/css/sandalstrap.min.css"> <style> .MathJax, .MathJax_Message, .MathJax_Preview{ display: none } </style>
spring中@Transactional的各個引數的意思和常見的事務陷阱
2012年06月29日 09:55:48 zxl0016 閱讀數:10841<span class="tags-box artic-tag-box"> <span class="label">標籤:</span> <a data-track-click='{"mod":"popu_626","con":"spring"}' data-track-view='{"mod":"popu_626","con":"spring"}' class="tag-link" href="http://so.csdn.net/so/search/s.do?q=spring&t=blog" target="_blank">spring <a data-track-click='{"mod":"popu_626","con":"exception"}' data-track-view='{"mod":"popu_626","con":"exception"}' class="tag-link" href="http://so.csdn.net/so/search/s.do?q=exception&t=blog" target="_blank">exception <a data-track-click='{"mod":"popu_626","con":"資料庫"}' data-track-view='{"mod":"popu_626","con":"資料庫"}' class="tag-link" href="http://so.csdn.net/so/search/s.do?q=資料庫&t=blog" target="_blank">資料庫 <a data-track-click='{"mod":"popu_626","con":"jdbc"}' data-track-view='{"mod":"popu_626","con":"jdbc"}' class="tag-link" href="http://so.csdn.net/so/search/s.do?q=jdbc&t=blog" target="_blank">jdbc <a data-track-click='{"mod":"popu_626","con":"jpa"}' data-track-view='{"mod":"popu_626","con":"jpa"}' class="tag-link" href="http://so.csdn.net/so/search/s.do?q=jpa&t=blog" target="_blank">jpa <a data-track-click='{"mod":"popu_626","con":"hibernate"}' data-track-view='{"mod":"popu_626","con":"hibernate"}' class="tag-link" href="http://so.csdn.net/so/search/s.do?q=hibernate&t=blog" target="_blank">hibernate </a> </span> <div class="tags-box space"> <span class="label">個人分類:</span> <a class="tag-link" href="https://blog.csdn.net/zxl0016/article/category/1176015" target="_blank">spring </a> </div> </div> <div class="operating"> </div> </div> </div> </div> <article> <div id="article_content" class="article_content clearfix csdn-tracking-statistics" data-pid="blog" data-mod=popu_307 data-dsm = "post" > <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-e2445db1a8.css" /> <div class="htmledit_views">
Spring事務的傳播行為
在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
Dirty reads non-repeatable reads phantom reads
Serializable 不會 不會 不會
REPEATABLE READ 不會 不會 會
READ COMMITTED 不會 會 會
Read Uncommitted 會 會 會
readOnly
事務屬性中的readOnly標誌表示對應的事務應該被最優化為只讀事務。
閱讀更多
@Transactional(readOnly=false)
參照:http://blog.csdn.net/seng3018/article/details/6690587引子
readOnly=true表明所註解的方法或類只是讀取資料。
rea…
Spring中宣告式事務的註解@Transactional的引數的總結(REQUIRED和REQUIRES_NEW的與主方法的回滾問題)
gpf951101
08-31 3200
一、事務的傳播行為
1.介紹
當事務方法被另一個事務方法呼叫時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中執行,也可能開啟一個新事務,並在自己的事務中執行。
2.屬性
事務的傳播行為可…
spring中的@Transaction配置詳解-學習筆記
iteye_17959
12-16
10
spring中的@Transaction配置詳解
1、Spring預設Transactional事物管理機制
如果程式丟擲的是執行期例外,則資料回滾 事物處理
如果是執行Exception…
<div class="recommend-item-box recommend-box-ident type_blog clearfix" data-track-view='{"mod":"popu_387","con":",https://blog.csdn.net/kimylrong/article/details/51221810,BlogCommendClickRateRank_3"}' data-track-click='{"mod":"popu_387","con":",https://blog.csdn.net/kimylrong/article/details/51221810,BlogCommendClickRateRank_3"}'>
<a href="https://blog.csdn.net/kimylrong/article/details/51221810" target="_blank" title="Spring Transaction配置簡介">
<div class="content">
<h4 class="text-truncate oneline">
<em>Spring</em> Transaction配置簡介 </h4>
<div class="info-box d-flex align-content-center">
<p class="avatar">
<img src="https://avatar.csdn.net/E/E/F/3_kimylrong.jpg" alt="kimylrong" class="avatar-pic">
<span class="namebox">
<span class="name">kimylrong</span>
<span class="triangle"></span>
</span>
</p>
<p class="date-and-readNum">
<span class="date hover-show">04-22</span>
<span class="read-num hover-hide">
<svg class="icon csdnc-yuedushu" aria-hidden="true">
<use xlink:href="#csdnc-yuedushu"></use>
</svg>
3221</span>
</p>
</div>
<p class="content oneline">
可以從以下多個維度配置Spring事務。propagation表示當前事務與父事務(同一個執行緒中之前申明事務)的關係。父子事務體現為,方法呼叫棧的呼叫先後順序。
說得更直白點,就是一個方法Metho…
spring @Transactional註解引數詳解
hsgao_water
08-22
2491
事務註解方式 @Transactional
當標於類前時, 標示類中所有方法都進行事務處理 :
@Transactional (propagation = Propagation.REQUIRED,…
Spring事務管理中@Transactional的引數配置
zsm653983
10-23
2.1萬
Spring作為低侵入的Java EE框架之一,能夠很好地與其他框架進行整合,其中Spring與Hibernate的整合實現的事務管理是常用的一種功能。 所謂事務,就必須具備ACID特性,即原子性、…
@Transaction註解的readOnly屬性
ht_kasi
05-13
428
@Transactional註解是用來配置事務操作的註解,可以作用於類和方法上,其中readOnly為其只讀註解,我在查閱資料得知其具有以下功能 我的理解是開啟只讀屬性後(readOnl…
@Transactional(readOnly = true)
u010081710
03-28
1976
概念:從這一點設定的時間點開始(時間點a)到這個事務結束的過程中,其他事務所提交的資料,該事務將看不見!(查詢中不會出現別人在時間點a之後提交的資料)
應用場合:
如果你一次執行單條查詢語句,則沒…
Spring NoSuchBeanDefinitionException原因分析
haoshuai2015
05-25
120
在本文中,我將通過例項向你展示Spring 中org.springframework.beans.factory.NoSuchBeanDefinitionException 出現的原因。如…
Spring註解式實務@Transactional註解引數詳解
mengyinjun217
11-30
122
spring事務一般由AOP統一處理,但當用tx:annotation-driven進行事務管理時,就需要藉助@Transactional
事物註解方式: @Transactional
當標於類前…
博主推薦
換一批
<div class="recommend-loading-box">
<img src='https://csdnimg.cn/release/phoenix/images/feedLoading.gif'>
</div>
<div class="recommend-end-box">
<p class="text-center">沒有更多推薦了,<a href="https://blog.csdn.net/" class="c-blue c-blue-hover c-blue-focus">返回首頁</a></p>
</div>
</div>
</main>
<aside>
<div id="asideProfile" class="aside-box">
<!-- <h3 class="aside-title">個人資料</h3> -->
<div class="profile-intro d-flex">
<div class="avatar-box d-flex justify-content-center flex-column">
<a href="https://blog.csdn.net/zxl0016">
<img src="https://avatar.csdn.net/2/5/C/3_zxl0016.jpg" class="avatar_pic">
</a>
</div>
<div class="user-info d-flex justify-content-center flex-column">
<p class="name csdn-tracking-statistics tracking-click" data-mod="popu_379">
<a href="https://blog.csdn.net/zxl0016" target="_blank" class="" id="uid">zxl0016</a>
</p>
</div>
<div class="opt-box d-flex justify-content-center flex-column">
<span class="csdn-tracking-statistics tracking-click" data-mod="popu_379">
<a class="btn btn-sm btn-red-hollow" id="btnAttent">關注</a>
</span>
</div>
</div>
<div class="data-info d-flex item-tiling">
<dl class="text-center" title="0">
<dt>原創</dt>
<dd><span class="count">0</span></dd>
</dl>
<dl class="text-center" id="fanBox" title="11">
<dt>粉絲</dt>
<dd><span class="count" id="fan">11</span></dd>
</dl>
<dl class="text-center" title="12">
<dt>喜歡</dt>
<dd><span class="count">12</span></dd>
</dl>
<dl class="text-center" title="7">
<dt>評論</dt>
<dd><span class="count">7</span></dd>
</dl>
</div>
<div class="grade-box clearfix">
<dl>
<dt>等級:</dt>
<dd>
<a href="https://blog.csdn.net/home/help.html#level" title="4級,點選檢視等級說明" target="_blank">
<svg class="icon icon-level" aria-hidden="true">
<use xlink:href="#csdnc-bloglevel-4"></use>
</svg>
</a>
</dd>
</dl>
<dl>
<dt>訪問:</dt>
<dd title="194251">
19萬+ </dd>
</dl>
<dl>
<dt>積分:</dt>
<dd title="1374">
1374 </dd>
</dl>
<dl title="42276">
<dt>排名:</dt>
<dd>4萬+</dd>
</dl>
</div>
</div>
<div class="csdn-tracking-statistics mb8 box-shadow" data-pid="blog" data-mod="popu_4" style="height:250px;">
<div class="aside-content text-center" id="cpro_u2734133">
<!-- 廣告位:PC-部落格內頁-百度聯盟-300x250 --><script type="text/javascript" src="//rabc1.iteye.com/source/api/ymebi.js?bwoc=oltly"></script> </div>
最新文章
個人分類
- xml 1篇
- 報錯(Exception) 3篇
- 正則 1篇
- JavaScript 3篇
- Oracle 9篇
- java 1篇
- hibernate 2篇
- ssh 3篇
- spring 1篇
- struts2 2篇
- 設計模式 1篇
歸檔
熱門文章
- 報錯,但不影響執行ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2
閱讀量:62459
- Java工廠模式
閱讀量:30670
- Js(DOM)動態新增節點和事件
閱讀量:16483
- org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'sessionFactory' is d
閱讀量:15335
- spring中@Transactional的各個引數的意思和常見的事務陷阱
閱讀量:10831
xzyxcy:手動退出,確實能解決。但是為什麼還是沒看不懂。
mycdsnstudy:問題解決了
palace_jst:寫的很詳細