事務(wù)是邏輯上的一組操作,要么都執(zhí)行,要么都不執(zhí)行。
我們系統(tǒng)的每個業(yè)務(wù)方法可能包括了多個原子性的數(shù)據(jù)庫操作,比如下面的 savePerson()
方法中就有兩個原子性的數(shù)據(jù)庫操作。這些原子性的數(shù)據(jù)庫操作是有依賴的,它們要么都執(zhí)行,要不就都不執(zhí)行。
public void savePerson() {
personDao.save(person);
personDetailDao.save(personDetail);
}
另外,需要格外注意的是:事務(wù)能否生效數(shù)據(jù)庫引擎是否支持事務(wù)是關(guān)鍵。比如常用的 MySQL 數(shù)據(jù)庫默認使用支持事務(wù)的innodb
引擎。但是,如果把數(shù)據(jù)庫引擎變?yōu)?nbsp;myisam
,那么程序也就不再支持事務(wù)了!
事務(wù)最經(jīng)典也經(jīng)常被拿出來說例子就是轉(zhuǎn)賬了。假如小明要給小紅轉(zhuǎn)賬 1000 元,這個轉(zhuǎn)賬會涉及到兩個關(guān)鍵操作就是:
將小明的余額減少 1000 元
將小紅的余額增加 1000 元。
萬一在這兩個操作之間突然出現(xiàn)錯誤比如銀行系統(tǒng)崩潰或者網(wǎng)絡(luò)故障,導(dǎo)致小明余額減少而小紅的余額沒有增加,這樣就不對了。事務(wù)就是保證這兩個關(guān)鍵操作要么都成功,要么都要失敗。
public class OrdersService {
private AccountDao accountDao;
public void setOrdersDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)
public void accountMoney() {
//小紅賬戶多1000
accountDao.addMoney(1000,xiaohong);
//模擬突然出現(xiàn)的異常,比如銀行中可能為突然停電等等
//如果沒有配置事務(wù)管理的話會造成,小紅賬戶多了1000而小明賬戶沒有少錢
int i = 10 / 0;
//小王賬戶少1000
accountDao.reduceMoney(1000,xiaoming);
}
}
另外,數(shù)據(jù)庫事務(wù)的 ACID 四大特性是事務(wù)的基礎(chǔ),下面簡單來了解一下。
再提醒一次:你的程序是否支持事務(wù)首先取決于數(shù)據(jù)庫 ,比如使用 MySQL 的話,如果你選擇的是 innodb 引擎,那么恭喜你,是可以支持事務(wù)的。但是,如果你的 MySQL 數(shù)據(jù)庫使用的是 myisam 引擎的話,那不好意思,從根上就是不支持事務(wù)的。
這里再多提一下一個非常重要的知識點: MySQL 怎么保證原子性的?
我們知道如果想要保證事務(wù)的原子性,就需要在異常發(fā)生時,對已經(jīng)執(zhí)行的操作進行回滾,在 MySQL 中,恢復(fù)機制是通過 回滾日志(undo log) 實現(xiàn)的,所有事務(wù)進行的修改都會先先記錄到這個回滾日志中,然后再執(zhí)行相關(guān)的操作。如果執(zhí)行過程中遇到異常的話,我們直接利用 回滾日志 中的信息將數(shù)據(jù)回滾到修改之前的樣子即可!并且,回滾日志會先于數(shù)據(jù)持久化到磁盤上。這樣就保證了即使遇到數(shù)據(jù)庫突然宕機等情況,當用戶再次啟動數(shù)據(jù)庫的時候,數(shù)據(jù)庫還能夠通過查詢回滾日志來回滾將之前未完成的事務(wù)。
通過 TransactionTemplate
或者TransactionManager
手動管理事務(wù),實際應(yīng)用中很少使用,但是對于你理解 Spring 事務(wù)管理原理有幫助。
使用TransactionTemplate
進行編程式事務(wù)管理的示例代碼如下:
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 業(yè)務(wù)代碼
} catch (Exception e){
//回滾
transactionStatus.setRollbackOnly();
}
}
});
}
使用 TransactionManager
進行編程式事務(wù)管理的示例代碼如下:
@Autowired
private PlatformTransactionManager transactionManager;
public void testTransaction() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// .... 業(yè)務(wù)代碼
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
推薦使用(代碼侵入性最小),實際是通過 AOP 實現(xiàn)(基于@Transactional
的全注解方式使用最多)。
使用 @Transactional
注解進行事務(wù)管理的示例代碼如下:
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}
Spring 框架中,事務(wù)管理相關(guān)最重要的 3 個接口如下:
PlatformTransactionManager
: (平臺)事務(wù)管理器,Spring 事務(wù)策略的核心。TransactionDefinition
: 事務(wù)定義信息(事務(wù)隔離級別、傳播行為、超時、只讀、回滾規(guī)則)。TransactionStatus
: 事務(wù)運行狀態(tài)。我們可以把 PlatformTransactionManager
接口可以被看作是事務(wù)上層的管理者,而 TransactionDefinition
和 TransactionStatus
這兩個接口可以看作是事物的描述。
PlatformTransactionManager
會根據(jù) TransactionDefinition
的定義比如事務(wù)超時時間、隔離級別、傳播行為等來進行事務(wù)管理 ,而 TransactionStatus
接口則提供了一些方法來獲取事務(wù)相應(yīng)的狀態(tài)比如是否新事務(wù)、是否可以回滾等等。
Spring 并不直接管理事務(wù),而是提供了多種事務(wù)管理器 。Spring 事務(wù)管理器的接口是: PlatformTransactionManager
。
通過這個接口,Spring 為各個平臺如 JDBC(DataSourceTransactionManager
)、Hibernate(HibernateTransactionManager
)、JPA(JpaTransactionManager
)等都提供了對應(yīng)的事務(wù)管理器,但是具體的實現(xiàn)就是各個平臺自己的事情了。
PlatformTransactionManager
接口的具體實現(xiàn)如下:
PlatformTransactionManager
接口中定義了三個方法:
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager {
//獲得事務(wù)
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事務(wù)
void commit(TransactionStatus var1) throws TransactionException;
//回滾事務(wù)
void rollback(TransactionStatus var1) throws TransactionException;
}
這里多插一嘴。為什么要定義或者說抽象出來PlatformTransactionManager
這個接口呢?
主要是因為要將事務(wù)管理行為抽象出來,然后不同的平臺去實現(xiàn)它,這樣我們可以保證提供給外部的行為不變,方便我們擴展。我前段時間分享過:“為什么我們要用接口?”
事務(wù)管理器接口 PlatformTransactionManager
通過 getTransaction(TransactionDefinition definition)
方法來得到一個事務(wù),這個方法里面的參數(shù)是 TransactionDefinition
類 ,這個類就定義了一些基本的事務(wù)屬性。
那么什么是 事務(wù)屬性 呢?
事務(wù)屬性可以理解成事務(wù)的一些基本配置,描述了事務(wù)策略如何應(yīng)用到方法上。
事務(wù)屬性包含了 5 個方面:
TransactionDefinition
接口中定義了 5 個方法以及一些表示事務(wù)屬性的常量比如隔離級別、傳播行為等等。
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
// 返回事務(wù)的傳播行為,默認值為 REQUIRED。
int getPropagationBehavior();
//返回事務(wù)的隔離級別,默認值是 DEFAULT
int getIsolationLevel();
// 返回事務(wù)的超時時間,默認值為-1。如果超過該時間限制但事務(wù)還沒有完成,則自動回滾事務(wù)。
int getTimeout();
// 返回是否為只讀事務(wù),默認值為 false
boolean isReadOnly();
@Nullable
String getName();
}
TransactionStatus
接口用來記錄事務(wù)的狀態(tài) 該接口定義了一組方法,用來獲取或判斷事務(wù)的相應(yīng)狀態(tài)信息。
PlatformTransactionManager.getTransaction(…)
方法返回一個 TransactionStatus
對象。
TransactionStatus 接口接口內(nèi)容如下:
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事物
boolean hasSavepoint(); // 是否有恢復(fù)點
void setRollbackOnly(); // 設(shè)置為只回滾
boolean isRollbackOnly(); // 是否為只回滾
boolean isCompleted; // 是否已完成
}
實際業(yè)務(wù)開發(fā)中,大家一般都是使用 @Transactional
注解來開啟事務(wù),很多人并不清楚這個參數(shù)里面的參數(shù)是什么意思,有什么用。為了更好的在項目中使用事務(wù)管理,強烈推薦好好閱讀一下下面的內(nèi)容。
事務(wù)傳播行為是為了解決業(yè)務(wù)層方法之間互相調(diào)用的事務(wù)問題。
當事務(wù)方法被另一個事務(wù)方法調(diào)用時,必須指定事務(wù)應(yīng)該如何傳播。例如:方法可能繼續(xù)在現(xiàn)有事務(wù)中運行,也可能開啟一個新事務(wù),并在自己的事務(wù)中運行。
舉個例子!
我們在 A 類的aMethod()
方法中調(diào)用了 B 類的 bMethod()
方法。這個時候就涉及到業(yè)務(wù)層方法之間互相調(diào)用的事務(wù)問題。如果我們的 bMethod()
如果發(fā)生異常需要回滾,如何配置事務(wù)傳播行為才能讓 aMethod()
也跟著回滾呢?這個時候就需要事務(wù)傳播行為的知識了,如果你不知道的話一定要好好看一下。
Class A {
@Transactional(propagation=propagation.xxx)
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.xxx)
public void bMethod {
//do something
}
}
在TransactionDefinition
定義中包括了如下幾個表示傳播行為的常量:
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
......
}
不過如此,為了方便使用,Spring 會相應(yīng)地定義了一個枚舉類:Propagation
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum 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);
private final int value;
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
正確的事務(wù)傳播行為可能的值如下 :
1.TransactionDefinition.PROPAGATION_REQUIRED
使用的最多的一個事務(wù)傳播行為,我們平時經(jīng)常使用的@Transactional
注解默認使用就是這個事務(wù)傳播行為。如果當前存在事務(wù),則加入該事務(wù);如果當前沒有事務(wù),則創(chuàng)建一個新的事務(wù)。也就是說:
Propagation.REQUIRED
修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。Propagation.REQUIRED
的話,所有Propagation.REQUIRED
修飾的內(nèi)部方法和外部方法均屬于同一事務(wù) ,只要一個方法回滾,整個事務(wù)均回滾。舉個例子:如果我們上面的aMethod()
和bMethod()
使用的都是PROPAGATION_REQUIRED
傳播行為的話,兩者使用的就是同一個事務(wù),只要其中一個方法回滾,整個事務(wù)均回滾。
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void bMethod {
//do something
}
}
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
創(chuàng)建一個新的事務(wù),如果當前存在事務(wù),則把當前事務(wù)掛起。也就是說不管外部方法是否開啟事務(wù),Propagation.REQUIRES_NEW
修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。
舉個例子:如果我們上面的bMethod()
使用PROPAGATION_REQUIRES_NEW
事務(wù)傳播行為修飾,aMethod
還是用PROPAGATION_REQUIRED
修飾的話。如果aMethod()
發(fā)生異常回滾,bMethod()
不會跟著回滾,因為 bMethod()
開啟了獨立的事務(wù)。但是,如果 bMethod()
拋出了未被捕獲的異常并且這個異常滿足事務(wù)回滾規(guī)則的話,aMethod()
同樣也會回滾,因為這個異常被 aMethod()
的事務(wù)管理機制檢測到了。
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.REQUIRES_NEW)
public void bMethod {
//do something
}
}
3.TransactionDefinition.PROPAGATION_NESTED
:
如果當前存在事務(wù),則創(chuàng)建一個事務(wù)作為當前事務(wù)的嵌套事務(wù)來運行;如果當前沒有事務(wù),則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED
。也就是說:
Propagation.NESTED
和Propagation.REQUIRED
作用相同,修飾的內(nèi)部方法都會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。Propagation.NESTED
修飾的內(nèi)部方法屬于外部事務(wù)的子事務(wù),外部主事務(wù)回滾的話,子事務(wù)也會回滾,而內(nèi)部子事務(wù)可以單獨回滾而不影響外部主事務(wù)和其他子事務(wù)。這里還是簡單舉個例子:
如果 aMethod()
回滾的話,bMethod()
和bMethod2()
都要回滾,而bMethod()
回滾的話,并不會造成 aMethod()
和bMethod()
回滾。
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
b.bMethod();
b.bMethod2();
}
}
Class B {
@Transactional(propagation=propagation.PROPAGATION_NESTED)
public void bMethod {
//do something
}
@Transactional(propagation=propagation.PROPAGATION_NESTED)
public void bMethod2 {
//do something
}
}
4.TransactionDefinition.PROPAGATION_MANDATORY
如果當前存在事務(wù),則加入該事務(wù);如果當前沒有事務(wù),則拋出異常。(mandatory:強制性)
這個使用的很少,就不舉例子來說了。
若是錯誤的配置以下 3 種事務(wù)傳播行為,事務(wù)將不會發(fā)生回滾,這里不對照案例講解了,使用的很少。
TransactionDefinition.PROPAGATION_SUPPORTS
: 如果當前存在事務(wù),則加入該事務(wù);如果當前沒有事務(wù),則以非事務(wù)的方式繼續(xù)運行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED
: 以非事務(wù)方式運行,如果當前存在事務(wù),則把當前事務(wù)掛起。TransactionDefinition.PROPAGATION_NEVER
: 以非事務(wù)方式運行,如果當前存在事務(wù),則拋出異常。更多關(guān)于事務(wù)傳播行為的內(nèi)容請看這篇文章:《太難了~面試官讓我結(jié)合案例講講自己對 Spring 事務(wù)傳播行為的理解?!?/a>
TransactionDefinition
接口中定義了五個表示隔離級別的常量:
public interface TransactionDefinition {
......
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
......
}
和事務(wù)傳播行為這塊一樣,為了方便使用,Spring 也相應(yīng)地定義了一個枚舉類:Isolation
public enum 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);
private final int value;
Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
下面我依次對每一種事務(wù)隔離級別進行介紹:
TransactionDefinition.ISOLATION_DEFAULT
:使用后端數(shù)據(jù)庫默認的隔離級別,MySQL 默認采用的 REPEATABLE_READ
隔離級別 Oracle 默認采用的 READ_COMMITTED
隔離級別.TransactionDefinition.ISOLATION_READ_UNCOMMITTED
:最低的隔離級別,使用這個隔離級別很少,因為它允許讀取尚未提交的數(shù)據(jù)變更,可能會導(dǎo)致臟讀、幻讀或不可重復(fù)讀TransactionDefinition.ISOLATION_READ_COMMITTED
: 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù),可以阻止臟讀,但是幻讀或不可重復(fù)讀仍有可能發(fā)生TransactionDefinition.ISOLATION_REPEATABLE_READ
: 對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改,可以阻止臟讀和不可重復(fù)讀,但幻讀仍有可能發(fā)生。TransactionDefinition.ISOLATION_SERIALIZABLE
: 最高的隔離級別,完全服從 ACID 的隔離級別。所有的事務(wù)依次逐個執(zhí)行,這樣事務(wù)之間就完全不可能產(chǎn)生干擾,也就是說,該級別可以防止臟讀、不可重復(fù)讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。因為平時使用 MySQL 數(shù)據(jù)庫比較多,這里再多提一嘴!
MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ
(可重讀)。我們可以通過SELECT @@tx_isolation;
命令來查看,MySQL 8.0 該命令改為SELECT @@transaction_isolation;
:
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
這里需要注意的是:與 SQL 標準不同的地方在于 InnoDB 存儲引擎在 REPEATABLE-READ
(可重讀) 事務(wù)隔離級別下使用的是 Next-Key Lock 鎖算法,因此可以避免幻讀的產(chǎn)生,這與其他數(shù)據(jù)庫系統(tǒng)(如 SQL Server)是不同的。所以說 InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ
(可重讀) 已經(jīng)可以完全保證事務(wù)的隔離性要求,即達到了 SQL 標準的 SERIALIZABLE
(可串行化) 隔離級別。
因為隔離級別越低,事務(wù)請求的鎖越少,所以大部分數(shù)據(jù)庫系統(tǒng)的隔離級別都是 READ-COMMITTED
(讀取提交內(nèi)容) :,但是你要知道的是 InnoDB 存儲引擎默認使用 REPEATABLE-READ
(可重讀) 并不會什么任何性能上的損失。
更多關(guān)于事務(wù)隔離級別的內(nèi)容請看:
所謂事務(wù)超時,就是指一個事務(wù)所允許執(zhí)行的最長時間,如果超過該時間限制但事務(wù)還沒有完成,則自動回滾事務(wù)。在 TransactionDefinition
中以 int 的值來表示超時時間,其單位是秒,默認值為-1。
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
......
// 返回是否為只讀事務(wù),默認值為 false
boolean isReadOnly();
}
對于只有讀取數(shù)據(jù)查詢的事務(wù),可以指定事務(wù)類型為 readonly,即只讀事務(wù)。只讀事務(wù)不涉及數(shù)據(jù)的修改,數(shù)據(jù)庫會提供一些優(yōu)化手段,適合用在有多條數(shù)據(jù)庫查詢操作的方法中。
很多人就會疑問了,為什么我一個數(shù)據(jù)查詢操作還要啟用事務(wù)支持呢?
拿 MySQL 的 innodb 舉例子,根據(jù)官網(wǎng) https://dev.mysql.com/doc/refman/5.7/en/innodb-autocommit-commit-rollback.html 描述:
MySQL 默認對每一個新建立的連接都啟用了
autocommit
模式。在該模式下,每一個發(fā)送到 MySQL 服務(wù)器的sql
語句都會在一個單獨的事務(wù)中進行處理,執(zhí)行結(jié)束后會自動提交事務(wù),并開啟一個新的事務(wù)。
但是,如果你給方法加上了Transactional
注解的話,這個方法執(zhí)行的所有sql
會被放在一個事務(wù)中。如果聲明了只讀事務(wù)的話,數(shù)據(jù)庫就會去優(yōu)化它的執(zhí)行,并不會帶來其他的什么收益。
如果不加Transactional
,每條sql
會開啟一個單獨的事務(wù),中間被其它事務(wù)改了數(shù)據(jù),都會實時讀取到最新值。
分享一下關(guān)于事務(wù)只讀屬性,其他人的解答:
這些規(guī)則定義了哪些異常會導(dǎo)致事務(wù)回滾而哪些不會。默認情況下,事務(wù)只有遇到運行期異常(RuntimeException 的子類)時才會回滾,Error 也會導(dǎo)致事務(wù)回滾,但是,在遇到檢查型(Checked)異常時不會回滾。
如果你想要回滾你定義的特定的異常類型的話,可以這樣:
@Transactional(rollbackFor= MyException.class)
@Transactional
的作用范圍@Transactional
的常用配置參數(shù)@Transactional
注解源碼如下,里面包含了基本事務(wù)屬性的配置:
@Target({ElementType.TYPE, ElementType.METHOD})
@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 {};
}
@Transactional
的常用配置參數(shù)總結(jié)(只列巨額 5 個我平時比較常用的):
屬性名 | 說明 |
---|---|
propagation | 事務(wù)的傳播行為,默認值為 REQUIRED,可選的值在上面介紹過 |
isolation | 事務(wù)的隔離級別,默認值采用 DEFAULT,可選的值在上面介紹過 |
timeout | 事務(wù)的超時時間,默認值為-1(不會超時)。如果超過該時間限制但事務(wù)還沒有完成,則自動回滾事務(wù)。 |
readOnly | 指定事務(wù)是否為只讀事務(wù),默認值為 false。 |
rollbackFor | 用于指定能夠觸發(fā)事務(wù)回滾的異常類型,并且可以指定多個異常類型。 |
@Transactional
事務(wù)注解原理面試中在問 AOP 的時候可能會被問到的一個問題。簡單說下吧!
我們知道,@Transactional
的工作機制是基于 AOP 實現(xiàn)的,AOP 又是使用動態(tài)代理實現(xiàn)的。如果目標對象實現(xiàn)了接口,默認情況下會采用 JDK 的動態(tài)代理,如果目標對象沒有實現(xiàn)了接口,會使用 CGLIB 動態(tài)代理。
多提一嘴:createAopProxy()
方法 決定了是使用 JDK 還是 Cglib 來做動態(tài)代理,源碼如下:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
.......
}
如果一個類或者一個類中的 public 方法上被標注@Transactional
注解的話,Spring 容器就會在啟動的時候為其創(chuàng)建一個代理類,在調(diào)用被@Transactional
注解的 public 方法的時候,實際調(diào)用的是,TransactionInterceptor
類中的 invoke()
方法。這個方法的作用就是在目標方法之前開啟事務(wù),方法執(zhí)行過程中如果遇到異常的時候回滾事務(wù),方法調(diào)用完成之后提交事務(wù)。
TransactionInterceptor
類中的invoke()
方法內(nèi)部實際調(diào)用的是TransactionAspectSupport
類的invokeWithinTransaction()
方法。由于新版本的 Spring 對這部分重寫很大,而且用到了很多響應(yīng)式編程的知識,這里就不列源碼了。
若同一類中的其他沒有 @Transactional
注解的方法內(nèi)部調(diào)用有 @Transactional
注解的方法,有@Transactional
注解的方法的事務(wù)會失效。
這是由于Spring AOP
代理的原因造成的,因為只有當 @Transactional
注解的方法在類以外被調(diào)用的時候,Spring 事務(wù)管理才生效。
MyService
類中的method1()
調(diào)用method2()
就會導(dǎo)致method2()
的事務(wù)失效。
@Service
public class MyService {
private void method1() {
method2();
//......
}
@Transactional
public void method2() {
//......
}
}
解決辦法就是避免同一類中自調(diào)用或者使用 AspectJ 取代 Spring AOP 代理。
@Transactional
的使用注意事項總結(jié)@Transactional
注解只有作用到 public 方法上事務(wù)才生效,不推薦在接口上使用;@Transactional
注解的方法,這樣會導(dǎo)致事務(wù)失效;@Transactional
的 rollbackFor 和 propagation 屬性,否則事務(wù)可能會回滾失敗作者:Snailclimb
鏈接:Spring事務(wù)總結(jié)
來源:github