首頁  >  文章  >  Java  >  Spring事務的管理操作方法

Spring事務的管理操作方法

PHP中文网
PHP中文网原創
2017-06-20 16:20:291739瀏覽

疑問,確實像往常一樣在service上添加了註解 @Transactional,為什麼查詢資料庫時還是發現有資料不一致的情況,想想肯定是事務沒起作用,出現異常的時候數據沒有回滾。於是就對相關程式碼進行了一番測試,結果發現一下踩進了兩個坑,確實是事務未回滾導致的資料不一致。以下總結經驗教訓:

Spring事務的管理操作方法

  • 編程式的交易管理

    • 實際應用中很少使用

    • 透過使用TransactionTemplate 手動管理交易

  • 聲明式的事務管理

    • 開發中推薦使用(程式碼侵入最少)

    • Spring的宣告式交易是透過AOP實現的

主要掌握宣告式的交易管理。

spring事務不回滾的兩個原因

總結一下導致事務不回滾的兩個原因,一是Service類別內部方法調用,二是try...catch異常。

1. Service類別內部方法呼叫

大概就是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 會提交或回滾事務,大致流程如下圖:

Spring事務的管理操作方法

總結,在方法A 中呼叫方法B,實際上是透過「this」的引用,也就是直接調用了目標類的方法,而非透過Spring 上下文獲得的代理類,所以事務是不會開啟的。

2. try...catch異常

在一段業務邏輯中對資料庫異常進行了處理,使用了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官網。

17.5.3 聲明式事務的回滾

上一節中介紹瞭如何設置開啟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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn