使用spring中的@Transactional注解時,可能需要注意的地方


前情提要

在編寫業務層方法時,會遇到很多需要事務提交的操作,spring框架為我們提供很方便的做法,就是在需要事務提交的方法上添加@Transactional注解,比起我們自己開啟事務、提交以及控制回滾,要簡單的多。但是在使用的時候容易犯一些錯誤。我就自己的錯誤經歷總結如下。

枯燥的背景知識(可以忽略)

編程式事務管理&聲明式事務管理:

(一)編程式事務管理 在 Spring 出現以前,編程式事務管理對基於 POJO 的應用來說是唯一選擇。用過 Hibernate 的人都知道,我們需要在代碼中顯式調用beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是編程式事務管理。通過 Spring 提供的事務管理 API,我們可以在代碼中靈活控制事務的執行。在底層,Spring 仍然將事務操作委托給底層的持久化框架來執行。

(二)聲明式事務管理

1)Spring 的聲明式事務管理在底層是建立在 AOP 的基礎之上的。其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。

2)聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過等價的基於標注的方式),便可以將事務規則應用到業務邏輯中。因為事務管理本身就是一個典型的橫切邏輯,正是 AOP 的用武之地。Spring 開發團隊也意識到了這一點,為聲明式事務提供了簡單而強大的支持。

3)聲明式事務管理曾經是 EJB 引以為傲的一個亮點, Spring 讓 POJO 在事務管理方面也擁有了和 EJB 一樣的待遇,讓開發人員在 EJB 容器之外也用上了強大的聲明式事務管理功能,這主要得益於 Spring 依賴注入容器和 Spring AOP 的支持。依賴注入容器為聲明式事務管理提供了基礎設施,使得 Bean 對於 Spring 框架而言是可管理的;而 Spring AOP 則是聲明式事務管理的直接實現者。

4)建議在開發中使用聲明式事務,不僅因為其簡單,更主要是因為這樣使得純業務代碼不被污染,極大方便后期的代碼維護。和編程式事務相比,聲明式事務唯一不足地方是,后者的最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的代碼塊獨立為方法等等。


從@Transactional的代碼當中可以看到,可配置的參數主要有:

  1. 事務隔離級別: Isolation isolation() default Isolation.DEFAULT;
  2. 事務傳播行為:Propagation propagation() default Propagation.REQUIRED;
  3. 回滾、不回滾的異常類:rollbackFor() noRollbackFor()
  4. 超時:int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

mysql的innob引擎支持標准sql事務的四個級別:

REPEATABLE READ(默認)

 | READ COMMITTED

 | READ UNCOMMITTED

 | SERIALIZABLE

默認是可以重復讀,以repeatable_read 為例,默認情況它使用了Consistent Nonlocking Reads,其實就是多版本快照方式,一個repeatable_read 事務里面,第一次讀取從數據庫取,然后這個數據放入快照里面,后面的讀取都是從快照里面取,這樣雖然保證了一個事務里面讀取的數據的一致性,但是會出現其他事務已經改了數據庫里面的值,而當前事務卻還在使用老的數據。如果對於數據的同步要求很高的話,可以在sql上面使用鎖,

select … lock in share mode; //共享鎖,可同時讀,不能同時寫

select … for update; // 排他鎖,獨占

但是用鎖,肯定影響性能,至少mysql沒有使用鎖來實現repeatable-read隔離級別。

如果是SERIALIZABLE級別,mysql倒是會自動的將select轉化為select … LOCK IN SHARE MODE,即使用了共享鎖。


事務的傳播行為: 所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:

1、TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。

2、TransactionDefinition.PROPAGATION_REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起。

3、TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。

4、TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。

5、TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。

6、TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。

7、TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。

這里需要指出的是,前面的六種事務傳播行為是 Spring 從 EJB 中引入的,他們共享相同的概念。而 PROPAGATION_NESTED是 Spring 所特有的。以 PROPAGATION_NESTED 啟動的事務內嵌於外部事務中(如果存在外部事務的話),此時,內嵌事務並不是一個獨立的事務,它依賴於外部事務的存在,只有通過外部的事務提交,才能引起內部事務的提交,嵌套的子事務不能單獨提交。如果熟悉 JDBC 中的保存點(SavePoint)的概念,那嵌套事務就很容易理解了,其實嵌套的子事務就是保存點的一個應用,一個事務中可以包括多個保存點,每一個嵌套子事務。另外,外部事務的回滾也會導致嵌套子事務的回滾。


總結:

  1. Transactional 注解可以被應用於接口定義和接口方法、類定義和類的 public 方法上。
  2. “proxy-target-class” 屬性值來控制是基於接口的還是基於類的代理被創建。 <tx:annotation-driven transaction-manager=“transactionManager” proxy-target-class=“true”/> 注意:proxy-target-class屬性值決定是基於接口的還是基於類的代理被創建。如果proxy-target-class 屬性值被設置為true,那么基於類的代理將起作用(這時需要cglib庫)。如果proxy-target-class屬值被設置為false或者這個屬性被省略,那么標准的JDK 基於接口的代理。
  3. 注解@Transactional cglib與java動態代理最大區別是代理目標對象不用實現接口,那么注解要是寫到接口方法上,要是使用cglib代理,這是注解事物就失效了,為了保持兼容注解最好都寫到實現類方法上。
  4. Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現的任何接口上。在接口上使用 @Transactional 注解,只能當你設置了基於接口的代理時它才生效。因為注解是 不能繼承的,這就意味着如果正在使用基於類的代理時,那么事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝。
  5. @Transactional 的事務開啟 ,或者是基於接口的 或者是基於類的代理被創建。所以在同一個類中一個方法調用另一個方法有事務的方法,事務是不會起作用的。 原因:(這也是為什么在項目中有些@Async並沒有異步執行) spring 在掃描bean的時候會掃描方法上是否包含@Transactional注解,如果包含,spring會為這個bean動態地生成一個子類(即代理類,proxy),代理類是繼承原來那個bean的。此時,當這個有注解的方法被調用的時候,實際上是由代理類來調用的,代理類在調用之前就會啟動transaction。然而,如果這個有注解的方法是被同一個類中的其他方法調用的,那么該方法的調用並沒有通過代理類,而是直接通過原來的那個bean,所以就不會啟動transaction,我們看到的現象就是@Transactional注解無效。

Tips: 使用 ~for update ~實現實現悲觀鎖的時候,需要注意鎖的級別,Mysql InnoDB 默認行級鎖。行級鎖都是基於索引的,如果一條sql語句用不到索引,是不會使用行級鎖的,會使用表級,把整張表鎖住,這點需要注意。

Tips: 使用樂觀鎖時多數實現方法是使用版本號,或者時間戳。但是如果事務的隔離級別允許重復讀(比如:REPEATABLE_READ;mysql InnoDB默認也是這個級別),那么使用樂觀鎖是查詢不出版本或者時間戳的變化的,但是oracle的話默認就可以。

Tips: spring默認的事務隔離級別為底層數據區的隔離級別。所以,如果你用的是Mysql的InnoDB引擎,那么級別就是:REPEATABLE READ;如果你用的是oracle,那么級別就是READ COMMITED。

Tips: spring的@Transactional注解事務創博行為默認值為:PROPAGATION_REQUIRED

以上是我能想到的相關,如有錯誤希望大家及時和我探討。

相關查詢資料:

[http://zliguo.iteye.com/blog/2230013]

[http://my.oschina.net/guanzhenxing/blog/214228]

[http://blog.csdn.net/u012228718/article/details/42750119#t1]

[http://my.oschina.net/yybear/blog/103235]

[http://my.oschina.net/feichexia/blog/202520]

[http://www.hollischuang.com/archives/934]

[http://blog.csdn.net/clementad/article/details/47339519]

 

from: http://mojito515.github.io/blog/2016/08/31/transactionalinspring/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM