天天看點

@Transactional注解使用1.1 @Transactional介紹1.2 @Transactional注解屬性1.3 @Transactional注解的使用

1.1 @Transactional介紹

       @Transactional注解 可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public 方法将都具有該類型的事務屬性,同時,我們也可以在方法級别使用該标注來覆寫類級别的定義。

       雖然@Transactional 注解可以作用于接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該注解,因為這隻有在使用基于接口的代理時它才會生效。另外, @Transactional注解應該隻被應用到 public 方法上,這是由Spring AOP的本質決定的。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 注解,這将被忽略,也不會抛出任何異常。

       預設情況下,隻有來自外部的方法調用才會被AOP代理捕獲,也就是,類内部方法調用本類内部的其他方法并不會引起事務行為,即使被調用方法使用@Transactional注解進行修飾。

1.2 @Transactional注解屬性

       @Transactional注解裡面的各個屬性和咱們在上面講的事務屬性裡面是一一對應的。用來設定事務的傳播行為、隔離規則、復原規則、事務逾時、是否隻讀。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    /**
     * 當在配置檔案中有多個 TransactionManager , 可以用該屬性指定選擇哪個事務管理器。
     */
    @AliasFor("transactionManager")
    String value() default "";

    /**
     * 同上。
     */
    @AliasFor("value")
    String transactionManager() default "";

    /**
     * 事務的傳播行為,預設值為 REQUIRED。
     */
    Propagation propagation() default Propagation.REQUIRED;

    /**
     * 事務的隔離規則,預設值采用 DEFAULT。
     */
    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 {};

}
           

1.2.1 value、transactionManager屬性

       它們兩個是一樣的意思。當配置了多個事務管理器時,可以使用該屬性指定選擇哪個事務管理器。大多數項目隻需要一個事務管理器。然而,有些項目為了提高效率、或者有多個完全不同又不相幹的資料源,進而使用了多個事務管理器。機智的Spring的Transactional管理已經考慮到了這一點,首先定義多個transactional manager,并為qualifier屬性指定不同的值;然後在需要使用@Transactional注解的時候指定TransactionManager的qualifier屬性值或者直接使用bean名稱。配置和代碼使用的例子:

<tx:annotation-driven/>
 
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource1"></property>
    <qualifier value="datasource1Tx"/>
</bean>
 
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource2"></property>
    <qualifier value="datasource2Tx"/>
</bean>
           
public class TransactionalService {
 
    @Transactional("datasource1Tx")
    public void setSomethingInDatasource1() { ... }
 
    @Transactional("datasource2Tx")
    public void doSomethingInDatasource2() { ... }

}
           

1.2.2 propagation屬性

       propagation用于指定事務的傳播行為,預設值為 REQUIRED。propagation有七種類型,就是我們在上文中講到的事務屬性傳播行為的七種方式,如下所示:

propagation屬性 事務屬性-傳播行為 含義
REQUIRED TransactionDefinition.PROPAGATION_REQUIRED 如果目前沒有事務,就建立一個事務,如果已經存在一個事務,則加入到這個事務中。這是最常見的選擇。
SUPPORTS TransactionDefinition.PROPAGATION_SUPPORTS 支援目前事務,如果目前沒有事務,就以非事務方式執行。
MANDATORY TransactionDefinition.PROPAGATION_MANDATORY 表示該方法必須在事務中運作,如果目前事務不存在,則會抛出一個異常。
REQUIRES_NEW TransactionDefinition.PROPAGATION_REQUIRES_NEW 表示目前方法必須運作在它自己的事務中。一個新的事務将被啟動。如果存在目前事務,在該方法執行期間,目前事務會被挂起。
NOT_SUPPORTED TransactionDefinition.PROPAGATION_NOT_SUPPORTED 表示該方法不應該運作在事務中。如果目前存在事務,就把目前事務挂起。
NEVER TransactionDefinition.PROPAGATION_NEVER 表示目前方法不應該運作在事務上下文中。如果目前正有一個事務在運作,則會抛出異常。
NESTED TransactionDefinition.PROPAGATION_NESTED 如果目前存在事務,則在嵌套事務内執行。如果目前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

1.2.3 isolation屬性

       isolation用于指定事務的隔離規則,預設值為DEFAULT。@Transactional的隔離規則和上文事務屬性裡面的隔離規則也是一一對應的。總共五種隔離規則,如下所示:

@isolation屬性 事務屬性-隔離規則 含義 髒讀 不可重複讀 幻讀
DEFAULT TransactionDefinition.ISOLATION_DEFAULT 使用後端資料庫預設的隔離級别
READ_UNCOMMITTED TransactionDefinition.ISOLATION_READ_UNCOMMITTED 允許讀取尚未送出的資料變更(最低的隔離級别)
READ_COMMITTED TransactionDefinition.ISOLATION_READ_COMMITTED 允許讀取并發事務已經送出的資料
REPEATABLE_READ TransactionDefinition.ISOLATION_REPEATABLE_READ 對同一字段的多次讀取結果都是一緻的,除非資料是被本身事務自己所修改
SERIALIZABLE TransactionDefinition.ISOLATION_SERIALIZABLE 最高的隔離級别,完全服從ACID的隔離級别,也是最慢的事務隔離級别,因為它通常是通過完全鎖定事務相關的資料庫表來實作的

1.2.4 timeout

       timeout用于設定事務的逾時屬性。

1.2.5 readOnly

       readOnly用于設定事務是否隻讀屬性。

1.2.6 rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

       rollbackFor、rollbackForClassName用于設定那些異常需要復原;noRollbackFor、noRollbackForClassName用于設定那些異常不需要復原。他們就是在設定事務的復原規則。

1.3 @Transactional注解的使用

       @Transactional注解的使用關鍵點在了解@Transactional注解裡面各個參數的含義。這個咱們在上面已經對@Transactional注解參數的各個含義做了一個簡單的介紹。接下來,咱們着重講一講@Transactional注解使用過程中一些注意的點。

       @Transactional注解内部實作依賴于Spring AOP程式設計。而AOP在預設情況下,隻有來自外部的方法調用才會被AOP代理捕獲,也就是,類内部方法調用本類内部的其他方法并不會引起事務行為。

1.3.1 @Transactional 注解盡量直接加在方法上

       為什麼:因為@Transactional直接加在類或者接口上,@Transactional注解會對類或者接口裡面所有的public方法都有效(相當于所有的public方法都加上了@Transactional注解,而且注解帶的參數都是一樣的)。第一影響性能,可能有些方法我不需要@Transactional注解,第二方法不同可能@Transactional注解需要配置的參數也不同,比如有一個方法隻是做查詢操作,那咱們可能需要配置Transactional注解的readOnly參數。是以強烈建議@Transactional注解直接添加的需要的方法上。

1.3.2 @Transactional 注解必須添加在public方法上,private、protected方法上是無效的

       在使用@Transactional 的時候一定要記住,在private,protected方法上添加@Transactional 注解不會有任何效果。相當于沒加一樣。即使外部能調到protected的方法也無效。和沒有添加@Transactional一樣。

1.3.3 函數之間互相調用

       關于有@Transactional的函數之間調用,會産生什麼情況。這裡咱們通過幾個例子來說明。

2.3.3.1 同一個類中函數互相調用

       同一個類AClass中,有兩個函數aFunction、aInnerFunction。aFunction調用aInnerFunction。而且aFunction函數會被外部調用。

情況0: aFunction添加了@Transactional注解,aInnerFunction函數沒有添加。aInnerFunction抛異常。
public class AClass {

    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 資料庫操作A(增,删,該)
        aInnerFunction(); // 調用内部沒有添加@Transactional注解的函數
    }

    private void aInnerFunction() {
        //todo: 操作資料B(做了增,删,改 操作)
        throw new RuntimeException("函數執行有異常!");
    }

}
           

       結果:兩個函數操作的資料都會復原。

情況1:兩個函數都添加了@Transactional注解。aInnerFunction抛異常。
public class AClass {

    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 資料庫操作A(增,删,該)
        aInnerFunction(); // 調用内部沒有添加@Transactional注解的函數
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    private void aInnerFunction() {
        //todo: 操作資料B(做了增,删,改 操作)
        throw new RuntimeException("函數執行有異常!");
    }

}
           

       結果:同第一種情況一樣,兩個函數對資料庫操作都會復原。因為同一個類中函數互相調用的時候,内部函數添加@Transactional注解無效。@Transactional注解隻有外部調用才有效。

情況2: aFunction不添加注解,aInnerFunction添加注解。aInnerFunction抛異常。
public class AClass {

    public void aFunction() {
        //todo: 資料庫操作A(增,删,該)
        aInnerFunction(); // 調用内部沒有添加@Transactional注解的函數
    }

    @Transactional(rollbackFor = Exception.class)
    protected void aInnerFunction() {
        //todo: 操作資料B(做了增,删,改 操作)
        throw new RuntimeException("函數執行有異常!");
    }

}
           

       結果:兩個函數對資料庫的操作都不會復原。因為内部函數@Transactional注解添加和沒添加一樣。

情況3:aFunction添加了@Transactional注解,aInnerFunction函數沒有添加。aInnerFunction抛異常,不過在aFunction裡面把異常抓出來了。
public class AClass {

    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 資料庫操作A(增,删,該)
        try {
            aInnerFunction(); // 調用内部沒有添加@Transactional注解的函數
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void aInnerFunction() {
        //todo: 操作資料B(做了增,删,改 操作)
        throw new RuntimeException("函數執行有異常!");
    }

}
           

       結果:兩個函數裡面的資料庫操作都成功。事務復原的動作發生在當有@Transactional注解函數有對應異常抛出時才會復原。(當然了要看你添加的@Transactional注解有沒有效)。

1.3.3.1. 不同類中函數互相調用

       兩個類AClass、BClass。AClass類有aFunction、BClass類有bFunction。AClass類aFunction調用BClass類bFunction。最終會在外部調用AClass類的aFunction。

情況0:aFunction添加注解,bFunction不添加注解。bFunction抛異常。
@Service()
public class AClass {

    private BClass bClass;

    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }

    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 資料庫操作A(增,删,該)
        bClass.bFunction();
    }

}

@Service()
public class BClass {

    public void bFunction() {
        //todo: 資料庫操作A(增,删,該)
        throw new RuntimeException("函數執行有異常!");
    }
}
           

       結果:兩個函數對資料庫的操作都復原了。

情況1:aFunction、bFunction兩個函數都添加注解,bFunction抛異常。
@Service()
public class AClass {

    private BClass bClass;

    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }

    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 資料庫操作A(增,删,該)
        bClass.bFunction();
    }

}

@Service()
public class BClass {

    @Transactional(rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 資料庫操作A(增,删,該)
        throw new RuntimeException("函數執行有異常!");
    }
}
           

       結果:兩個函數對資料庫的操作都復原了。兩個函數裡面用的還是同一個事務。這種情況下,你可以認為事務rollback了兩次。兩個函數都有異常。

情況2:aFunction、bFunction兩個函數都添加注解,bFunction抛異常。aFunction抓出異常。
@Service()
public class AClass {

    private BClass bClass;

    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }

    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 資料庫操作A(增,删,該)
        try {
            bClass.bFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

@Service()
public class BClass {

    @Transactional(rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 資料庫操作A(增,删,該)
        throw new RuntimeException("函數執行有異常!");
    }
}
           

       結果:兩個函數資料庫操作都沒成功。而且還抛異常了。org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。看列印出來的解釋也很好了解把。咱們也可以這麼了解,兩個函數用的是同一個事務。bFunction函數抛了異常,調了事務的rollback函數。事務被标記了隻能rollback了。程式繼續執行,aFunction函數裡面把異常給抓出來了,這個時候aFunction函數沒有抛出異常,既然你沒有異常那事務就需要送出,會調事務的commit函數。而之前已經标記了事務隻能rollback-only(以為是同一個事務)。直接就抛異常了,不讓調了。

情況3:aFunction、bFunction兩個函數都添加注解,bFunction抛異常。aFunction抓出異常。這裡要注意bFunction函數@Transactional注解我們是有變化的,加了一個參數propagation = Propagation.REQUIRES_NEW,控制事務的傳播行為。表明是一個新的事務。其實咱們情況3就是來解決情況2的問題的。
@Service()
public class AClass {

    private BClass bClass;

    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }

    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 資料庫操作A(增,删,該)
        try {
            bClass.bFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

@Service()
public class BClass {

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 資料庫操作A(增,删,該)
        throw new RuntimeException("函數執行有異常!");
    }
}
           

       結果:bFunction函數裡面的操作復原了,aFunction裡面的操作成功了。有了前面情況2的了解。這種情況也很好解釋。兩個函數不是同一個事務了。

     總結:

  1. 要知道@Transactional注解裡面每個屬性的含義。@Transactional注解屬性就是來控制事務屬性的。通過這些屬性來生成事務。
  2. 要明确我們添加的@Transactional注解會不會起作用。@Transactional注解在外部調用的函數上才有效果,内部調用的函數添加無效,要切記。這是由AOP的特性決定的。
  3. 要明确事務的作用範圍,有@Transactional的函數調用有@Transactional的函數的時候,進入第二個函數的時候是新的事務,還是沿用之前的事務。稍不注意就會抛UnexpectedRollbackException異常。

繼續閱讀