源碼專題(三)Spring事務@Transactional的使用及原理

在Spring開發中,經常會用到這個註解,但它是怎麼起到開啓事務的作用的,還一知半解,今天就來分析一波它的用法和原理。

編程式事務和聲明式事務

    Spring 支持兩種事務管理方式:編程式和聲明式,編程式是在程序中顯式地使用TransactionTemplate來控制事務的開啓和提交。

​    聲明式事務是使用@Transactional註解,在類或方法上使用這個註解,就可以起到開啓事務的作用,聲明式事務是基於AOP的方式,在方法前開啓一個事務,在方法執行後進行commit,中間進行一些異常的判斷和處理。

​    相比來說,聲明式事務使用起來更加優雅,AOP的方式對代碼沒有侵入性,比較推薦在日常開發中使用。

聲明式事務的用法

@Transactional註解可以加在類或方法上,加在類上時是對該類的所有public方法開啓事務。加在方法上時也是隻對public方法起作用。另外@Transactional註解也可以加在接口上,但只有在設置了基於接口的代理時纔會生效,因爲註解不能繼承。所以該註解最好是加在類的實現上。

下面看一下@Transactional註解的各項參數。

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 -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

timtout

timtout是用來設置事務的超時時間,可以看到默認爲-1,不會超時。

isolation

isolation屬性是用來設置事務的隔離級別,數據庫有四種隔離級別:讀未提交、讀已提交、可重複讀、可串行化。MySQL的默認隔離級別是可重複讀。

public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),  // 讀未提交
    READ_COMMITTED(2),         // 讀已提交
    REPEATABLE_READ(4),     // 可重複讀
    SERIALIZABLE(8);            // 可串行化
}

readOnly

    readOnly屬性用來設置該屬性是否是隻讀事務,只讀事務要從兩方面來理解:它的功能是設置了只讀事務後在整個事務的過程中,其他事務提交的內容對當前事務是不可見的。

​    那爲什麼要設置只讀事務呢?它的好處是什麼?可以看出它的作用是保持整個事務的數據一致性,如果事務中有多次查詢,不會出現數據不一致的情況。所以在一個事務中如果有多次查詢,可以啓用只讀事務,如果只有一次查詢就無需只讀事務了。

​    另外,使用了只讀事務,數據庫會提供一些優化。

​    但要注意的是,只讀事務中只能有讀操作,不能含有寫操作,否則會報錯。

propagation

    propagation屬性用來設置事務的傳播行爲,對傳播行爲的理解,可以參考如下場景,一個開啓了事務的方法A,調用了另一個開啓了事務的方法B,此時會出現什麼情況?這就要看傳播行爲的設置了。

public enum Propagation {
    REQUIRED(0),                 // 如果有事務則加入,沒有則新建
    SUPPORTS(1),                // 如果已有事務就用,如果沒有就不開啓(繼承關係)
    MANDATORY(2),                // 必須在已有事務中
    REQUIRES_NEW(3),        // 不管是否已有事務,都要開啓新事務,老事務掛起
    NOT_SUPPORTED(4),   // 不開啓事務
    NEVER(5),                        // 必須在沒有事務的方法中調用,否則拋出異常
    NESTED(6);                    // 如果已有事務,則嵌套執行,如果沒有,就新建(和REQUIRED類似,和REQUIRES_NEW容易混淆)
}

REQUIRES_NEW 和 NESTED非常容易混淆,因爲它們都是開啓了一個新的事務。我去查詢了一下它們之間的區別,大概是這樣:

REQUIRES_NEW是開啓一個完全的全新事務,和當前事務沒有任何關係,可以單獨地失敗、回滾、提交。並不依賴外部事務。在新事務執行過程中,老事務是掛起的。

NESTED也是開啓新事務,但它開啓的是基於當前事務的子事務,如果失敗的話單獨回滾,但如果成功的話,並不會立即commit,而是等待外部事務的執行結果,外部事務commit時,子事務纔會commit。

rollbackFor

在@Transactional註解中,有一個重要屬性是roolbackFor,這是用來判斷在什麼異常下會進行回滾的,當方法內拋出指定的異常時,進行事務回滾。rollbackForClassName也是類似的。

​    rollbackFor有個問題是默認情況會做什麼,以前認爲默認會對所有異常進行回滾,但其實默認情況下只對RuntimeException回滾。

noRollbackFor

這個和上面正好相反,用來設置出現指定的異常時,不進行回滾。

Spring事務實現機制原理

我們來分析一下Spring事務管理機制的實現原理。由於Spring內置AOP默認使用動態代理模式實現,我們就先來分析一下動態代理模式的實現方法。動態代理模式的核心就在於代碼中不出現與具體應用層相關聯的接口或者類引用,如上所說,這個代理類適用於任何接口的實現。下面我們來看一個例子。

public class TxHandler implements InvocationHandler {
private Object originalObject;
public Object bind(Object obj) {
 this.originalObject = obj;
 return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
 Object result = null;
 if (!method.getName().startsWith("save")) {
  UserTransaction tx = null;
  try {
   tx = (UserTransaction) (new InitialContext().lookup("java/tx"));
   result = method.invoke(originalObject, args);
   tx.commit();
  } catch (Exception ex) {
   if (null != tx) {
    try {
     tx.rollback();
    } catch (Exception e) {
   }
  }
 }
} else {
 result = method.invoke(originalObject, args);
}
return result;
}
}

下面我們來分析一下上述代碼的關鍵所在。

  首先來看一下這段代碼:

return Proxy.newProxyInstance(
 obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);

   java.lang.reflect.Proxy.newProxyInstance方法根據傳入的接口類型 (obj.getClass.getInterfaces())動態構造一個代理類實例返回,這也說明了爲什麼動態代理實現要求其所代理的對象一定要實現一個接口。這個代理類實例在內存中是動態構造的,它實現了傳入的接口列表中所包含的所有接口。

  再來分析以下代碼:

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
 ……
 result = method.invoke(originalObject, args);
 ……
 return result;
}


   InvocationHandler.invoke方法將在被代理類的方法被調用之前觸發。通過這個方法,我們可以在被代理類方法調用的前後進行一些處理,如代碼中所示,InvocationHandler.invoke方法的參數中傳遞了當前被調用的方法(Method),以及被調用方法的參數。同時,可以通過method.invoke方法調用被代理類的原始方法實現。這樣就可以在被代理類的方法調用前後寫入任何想要進行的操作。

   Spring的事務管理機制實現的原理,就是通過這樣一個動態代理對所有需要事務管理的Bean進行加載,並根據配置在invoke方法中對當前調用的方法名進行判定,並在method.invoke方法前後爲其加上合適的事務管理代碼,這樣就實現了Spring式的事務管理。Spring中的AOP實現更爲複雜和靈活,不過基本原理是一致的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章