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的理解。這種情況也很好解釋。兩個函數不是同一個事務了。
總結:
-
要知道@Transactional註解裏面每個屬性的含義。@Transactional註解屬性就是來控制事務屬性的。通過這些屬性來生成事務。
-
要明確我們添加的@Transactional註解會不會起作用。@Transactional註解在外部調用的函數上纔有效果,內部調用的函數添加無效,要切記。這是由AOP的特性決定的。
-
要明確事務的作用範圍,有@Transactional的函數調用有@Transactional的函數的時候,進入第二個函數的時候是新的事務,還是沿用之前的事務。稍不注意就會拋UnexpectedRollbackException異常。