疑問,確實像往常一樣在service上添加了註解
@Transactional
,為什麼查詢資料庫時還是發現有資料不一致的情況,想想肯定是事務沒起作用,出現異常的時候數據沒有回滾。於是就對相關程式碼進行了一番測試,結果發現一下踩進了兩個坑,確實是事務未回滾導致的資料不一致。以下總結經驗教訓:
編程式的交易管理
實際應用中很少使用
透過使用TransactionTemplate
手動管理交易
聲明式的事務管理
開發中推薦使用(程式碼侵入最少)
Spring的宣告式交易是透過AOP實現的
主要掌握宣告式的交易管理。
總結一下導致事務不回滾的兩個原因,一是Service類別內部方法調用,二是try...catch異常。
大概就是Service 中有一個方法A,會內部呼叫方法B, 方法A 沒有事務管理,方法B 採用了宣告式事務,透過在方法上聲明Transactional 的註解來做事務管理。範例程式碼如下:
@Servicepublic class RabbitServiceImpl implements RabbitService { @Autowiredprivate RabbitDao rabbitDao; @Autowiredprivate TortoiseDao tortoiseDao; @Overridepublic Rabbit methodA(String name){return methodB(name); } @Transactional(propagation = Propagation.REQUIRED)public boolean methodB(String name){ rabbitDao.insertRabbit(name); tortoiseDao.insertTortoise(name);return true; } }
單元測試程式碼如下:
public class RabbitServiceImplTest { @Autowiredprivate RabbitService rabbitService;// 事务未开启 @Testpublic void testA(){ rabbitService.methodA("rabbit"); }// 事务开启 @Testpublic void testB(){ rabbitService.methodB("rabbit"); } }
從上一節可以看到,宣告式交易是通通過AOP動態代理實現的,這樣會產生一個代理類別來做事務管理,而目標類別(service)本身是不能感知代理類別的存在的。
對於加了@Transactional註解的方法來說,在呼叫代理類別的方法時,會先透過攔截器TransactionInterceptor開啟事務,然後在呼叫目標類別的方法,最後在呼叫結束後,TransactionInterceptor 會提交或回滾事務,大致流程如下圖:
總結,在方法A 中呼叫方法B,實際上是透過「this」的引用,也就是直接調用了目標類的方法,而非透過Spring 上下文獲得的代理類,所以事務是不會開啟的。
在一段業務邏輯中對資料庫異常進行了處理,使用了try...catch子句捕獲異常並throw了一個自定義異常,這種情況導致了事務未回滾,範例程式碼如下:
@Transactional(propagation = Propagation.REQUIRED)public boolean methodB(String name) throws BizException {try { rabbitDao.insertRabbit(name); tortoiseDao.insertTortoise(name); } catch (Exception e) {throw new BizException(ReturnCode.EXCEPTION.code, ReturnCode.EXCEPTION.msg); }return true; }
BizException的定義如下:
public class BizException extends Exception {// 自定义异常}
上面程式碼中的聲明式事務在出現異常的時候,事務是不會回滾的。在程式碼中我雖然捕獲了異常,但是同時我也拋出了異常,為什麼事務未回滾呢?猜測是異常類型不對,於是開始查詢原因,翻看了Spring的官方文檔,找到了答案。下面是翻譯自Spring官網。
上一節中介紹瞭如何設置開啟Spring事務,一般在你的應用的Service層代碼中設置,這一節將介紹在簡單流行的聲明式交易中如何控制交易回滾。
在Spring FrameWork 的交易框架中推薦的交易回滾方法是,在目前執行的交易上下文中拋出一個例外。如果異常未被處理,當拋出異常呼叫堆疊的時候,Spring FrameWork 的交易框架程式碼將捕獲任何未處理的異常,然後並決定是否將此交易標記為回滾。
在預設設定中,Spring FrameWork 的交易框架程式碼只會將出現runtime, unchecked
例外的交易標記為回溯;也就是說交易中拋出的異常時RuntimeException或其子類,這樣事務才會回滾(預設情況下Error也會導致事務回滾)。在預設設定的情況下,所有的 checked 異常都不會造成交易回滾。
註:Unchecked Exception包含Error與RuntimeException. RuntimeException的所有子類別也都屬於此類。另一類就是checked Exception。
你可以精確的設定異常類型,指定此異常類別交易回滾,包括 checked 例外。下面的xml程式碼片段展示如何設定checked異常會造成交易回滾,應用自訂例外類型:
<advice> <attributes> <method></method> <method></method> </attributes> </advice>
與其有同等作用的註解形式如下:
@Transactional(rollbackForClassName={"Exception"}) 或者 @Transactional(rollbackFor={Exception.class})
在你遇到异常不想回滚事务的时候,同样的你也可指定不回滚的规则,下面的一个例子告诉你,即使遇到未处理的 InstrumentNotFoundException
异常时,Spring FrameWork 的事务框架同样会提交事务,而不回滚。
<advice> <attributes> <method></method> <method></method> </attributes> </advice>
与其有同样作用的注解形式如下:
@Transactional(noRollbackForClassName={"InstrumentNotFoundException"}) 或者 @Transactional(noRollbackFor={InstrumentNotFoundException.class})
还有更灵活的回滚规则配置方法,同时指定什么异常回滚,什么异常不回滚。当Spring FrameWork 的事务框架捕获到一个异常的时候,会去匹配配置的回滚规则来决定是否标记回滚事务,使用匹配度最强的规则结果。因此,下面的配置例子表达的意思是,除了异常 InstrumentNotFoundException
之外的任何异常都会导致事务回滚。
<advice> <attributes> <method></method> </attributes> </advice>
你也可以通过编程式的方式回滚一个事务,尽管方法非常简单,但是也有非常强的代码侵入性,使你的业务代码和Spring FrameWork 的事务框架代码紧密的绑定在一起,示例代码如下:
public void resolvePosition() { try { // some business logic... } catch (NoProductInStockException ex) { // trigger rollback programmatically TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
如果可能的话,强烈推荐您使用声明式事务方式回滚事务,对于编程式事务,如果你强烈需要它,也是可以使用的,but its usage flies in the face of achieving a clean POJO-based architecture.(没懂...)
看完官方文档这节内容找到了问题的答案,原来是因为我们自定义的异常不是 RuntimeException
。我的解决办法是,在注解@Transactional
中添加 rollbackFor={BizException.class}
。可能你会问我为什么不将自定义异常修改为继承RuntimeException,因为我需要BizException是一个checked 异常。
以上是Spring事務的管理操作方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!