Spring配置事務管理及事物傳播行為


Spring管理事務的方式有兩種:

1:事務類型

編程式事務:利用手動代碼編寫事務相關的業務邏輯,這種方式比較復雜、啰嗦,但是更加靈活可控制

public void testTransactionTemplate() {
 
  TransactionTemplate transactionTemplate = new TransactionTemplate(txManager); transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); //設置事務隔離級別 transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//設置為required傳播級別  .... transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { //事務塊 jdbcTemplate.update(INSERT_SQL, "test"); }}); }

 聲明式事務:為了避免我們每次都手動寫代碼,利用Spring AOP的方式對每個方法代理環繞,利用xml配置避免了寫代碼。

<tx:advice id="txAdvice" transaction-manager="txManager"> 
      <tx:attributes>  <!--設置所有匹配的方法,然后設置傳播級別和事務隔離-->
           <tx:method name="save*" propagation="REQUIRED" /> 
           <tx:method name="add*" propagation="REQUIRED" /> 
           <tx:method name="create*" propagation="REQUIRED" /> 
           <tx:method name="insert*" propagation="REQUIRED" /> 
           <tx:method name="update*" propagation="REQUIRED" /> 
           <tx:method name="merge*" propagation="REQUIRED" /> 
           <tx:method name="del*" propagation="REQUIRED" /> 
           <tx:method name="remove*" propagation="REQUIRED" /> 
           <tx:method name="put*" propagation="REQUIRED" /> 
           <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> 
           <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> 
          <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> 
          <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> 
          <tx:method name="*" propagation="SUPPORTS" read-only="true" /> 
     </tx:attributes> 
</tx:advice> 
<aop:config> 
       <aop:pointcut id="txPointcut" expression="execution(* org.transaction..service.*.*(..))" /> 
       <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> 
</aop:config>

同時也可以用注解的方式

<tx:annotation-driven transaction-manager="transactioManager" /><!--開啟注解的方式--> 
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED;//傳播級別  Isolation isolation() default Isolation.DEFAULT;//事務隔離級別 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;//事務超時時間 boolean readOnly() default false;//只讀事務 Class<? extends Throwable>[] rollbackFor() default {};//拋出哪些異常 會執行回滾 String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {};//不回滾的異常名稱  } //Transaction注解可以放在方法上或者類上

注意事項:

@Transactional注解可以作用在接口、類、類方法。
作用於類:當把@Transactional 注解放在類上時,表示所有該類的public方法都配置相同的事務屬性信息。
作用於方法:當類配置了@Transactional,方法也配置了@Transactional,方法的事務會覆蓋類的事務配置信息。
作用於接口:不推薦這種使用方法,因為一旦標注在Interface上並且配置了Spring AOP 使用CGLib動態代理,將會導致@Transactional注解失效

Transactional 注解可以被應用於接口定義和接口方法、類定義和類的 public 方法上。
請注意僅僅 @Transactional 注解的出現不足於開啟事務行為,並且只有配置了<tx:annotation-driven/>元素,@Transactional才會有效
Spring建議是你在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現的任何接口上。

@Transactional不生效的場景

 

1、用在public 修飾的方法上

 

如果在protected、private 修飾的方法上使用 @Transactional 注解,不會有任何報錯,但是事務無效

 

還是動態代理的原因,類內部方法的調用是通過this調用的,不會使用動態代理對象,事務不會回滾。

 

3、異常被處理了,比如使用了try..catch

 

Spring是根據拋出的異常來回滾的,如果異常被捕獲了沒有拋出的話,事務就不會回滾。

 

4、注解 rollbackFor屬性設置不對

 

Spring默認拋出unchecked異常或Error時才會回滾事務,要想其他類型異常也回滾則需要設置rollbackFor屬性的值。

 

5、數據庫引擎不支持事務
常用的MySQL數據庫默認使用支持事務的innodb引擎。一旦數據庫引擎切換成不支持事務的myisam,那事務就從根本上失效了

 

 

 

 

2:Spring事務傳播

Spring管理的事務是邏輯事務,而且物理事務和邏輯事務最大差別就在於事務傳播行為,事務傳播行為用於指定在多個事務方法間調用時,事務是如何在這些方法間傳播的,
Spring共支持7種傳播行為

@Transactional(propagation=Propagation.REQUIRED)
如果有事務, 那么加入事務, 沒有的話新建一個(默認情況下)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不為這個方法開啟事務
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
@Transactional(propagation=Propagation.MANDATORY)
必須在一個已有的事務中執行,否則拋出異常
@Transactional(propagation=Propagation.NEVER)
必須在一個沒有的事務中執行,否則拋出異常(與Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean調用這個方法,在其他bean中聲明事務,那就用事務.如果其他bean沒有聲明事務,那就不用事務.

為了演示事務傳播行為,我們新建一張用戶表

CEATE TABLE user (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(255) NOT NULL,
    `pwd` varchar(255) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=`InnoDB` AUTO_INCREMENT=10 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;

Required:

必須有邏輯事務,否則新建一個事務,使用PROPAGATION_REQUIRED指定,表示如果當前存在一個邏輯事務,則加入該邏輯事務,否則將新建一個邏輯事務,如下圖所示;

測試的代碼如下,在account插入的地方主動回滾

public int insertAccount(final String customer, final int money) {
    transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//設置為required傳播級別
   int re= transactionTemplate.execute(new TransactionCallback<Integer>() { public Integer doInTransaction( TransactionStatus status) { int i = accountDao.insertAccount(customer, money); status.setRollbackOnly();//主動回滾 return i; } }); return re; } public int inertUser(final String username, final String password) { transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//設置為required傳播級別 transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { int i = userDao.inertUser(username, password); int hahha = accountService.insertAccount("hahha", 2222); // status.setRollbackOnly(); System.out.println("user==="+i); System.out.println("account===="+hahha); } }); return 0; }

按照required的邏輯,代碼執行的邏輯如下:

  1. 在調用userService對象的insert方法時,此方法用的是Required傳播行為且此時Spring事務管理器發現還沒開啟邏輯事務,因此Spring管理器覺得開啟邏輯事務
  2. 在此邏輯事務中調用了accountService對象的insert方法,而在insert方法中發現同樣用的是Required傳播行為,因此直接使用該已經存在的邏輯事務;
  3. 返回userService,執行完並關閉事務

所以在這種情況下,兩個事務屬於同一個事務,一個回滾則兩個任務都回滾。

RequiresNew:

創建新的邏輯事務,使用PROPAGATION_REQUIRES_NEW指定,表示每次都創建新的邏輯事務(物理事務也是不同的)如下圖所示:

Supports:

支持當前事務,使用PROPAGATION_SUPPORTS指定,指如果當前存在邏輯事務,就加入到該邏輯事務,如果當前沒有邏輯事務,就以非事務方式執行,如下圖所示:

NotSupported:

不支持事務,如果當前存在事務則暫停該事務,使用PROPAGATION_NOT_SUPPORTED指定,即以非事務方式執行,如果當前存在邏輯事務,就把當前事務暫停,以非事務方式執行。

Mandatory:

必須有事務,否則拋出異常,使用PROPAGATION_MANDATORY指定,使用當前事務執行,如果當前沒有事務,則拋出異常(IllegalTransactionStateException)。當運行在存在邏輯事務中則以當前事務運行,如果沒有運行在事務中,則拋出異常

Never

不支持事務,如果當前存在是事務則拋出異常,使用PROPAGATION_NEVER指定,即以非事務方式執行,如果當前存在事務,則拋出異常(IllegalTransactionStateException)

Nested:

嵌套事務支持,使用PROPAGATION_NESTED指定,如果當前存在事務,則在嵌套事務內執行,如果當前不存在事務,則創建一個新的事務,嵌套事務使用數據庫中的保存點來實現,即嵌套事務回滾不影響外部事務,但外部事務回滾將導致嵌套事務回滾。

Nested和RequiresNew的區別:

  1. RequiresNew每次都創建新的獨立的物理事務,而Nested只有一個物理事務;
  2. Nested嵌套事務回滾或提交不會導致外部事務回滾或提交,但外部事務回滾將導致嵌套事務回滾,而 RequiresNew由於都是全新的事務,所以之間是無關聯的;
  3. Nested使用JDBC 3的保存點(save point)實現,即如果使用低版本驅動將導致不支持嵌套事務。

    使用嵌套事務,必須確保具體事務管理器實現的nestedTransactionAllowed屬性為true,否則不支持嵌套事務,如DataSourceTransactionManager默認支持,而HibernateTransactionManager默認不支持,需要設置來開啟。

3:事務隔離級別

關於spring的事務隔離級別與數據庫的一樣(詳見mysql事務),也是那四個,多了一個default
MYSQL: 默認為REPEATABLE_READ級別
SQLSERVER: 默認為READ_COMMITTED

比如下面的spring的配置方法:

<property name="transactionAttributes">
<props>
  <prop key="save*">PROPAGATION_REQUIRED</prop>
  <prop key="update*">PROPAGATION_REQUIRED</prop>
  <prop key="delete*">PROPAGATION_REQUIRED</prop>
  <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
   <prop key="find*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
</props>


就以find為例,可以這么配置,前面是控制傳播行為,后面是控制事務隔離級別的。那么這時哪怕數據庫層面上是重復讀(Repeatable read),但是還是以這里為准,你會發現在同一個事務中兩次查詢的結果是不一樣的。

最后問題,readonly這個屬性,是放在傳播行為中的, readonly並不能影響數據庫隔離級別,只是配置之后,不允許在事務中對數據庫進行修改操作,僅此而已。

 


免責聲明!

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



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