概述
簡單地說,如果我們有一個callMethod方法,并将其标記為@Transactional,Spring建立代理或操縱類位元組代碼,以管理事務的建立、送出和復原,僞代碼如下:
createTransactionIfNecessary();
try {
callMethod();
commitTransactionAfterReturning();
} catch (exception) {
completeTransactionAfterThrowing();
throw exception;
}
事務傳播(Propagation)
傳播定義了業務邏輯的事務邊界。Spring根據事務傳播的設定來啟動和暫停事務。Spring調用TransactionManager::getTransaction根據傳播屬性擷取或建立事務。
- REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) {
// ...
}
REQUIRED是預設傳播。Spring檢查是否存在活動事務,如果不存在,則建立一個新事務。否則,業務邏輯将附加到目前活動事務。僞代碼:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return createNewTransaction();
- SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) {
// ...
}
對于SUPPORTS,Spring首先檢查是否存在活動事務。如果存在事務,則将使用現有事務。如果沒有事務,則執行非事務。僞代碼:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return emptyTransaction;
- MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) {
// ...
}
當傳播為MANDATORY時,如果存在活動事務,則将使用它。如果沒有活動事務,則Spring抛出異常。僞代碼:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
throw IllegalTransactionStateException;
- NEVER
@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) {
// ...
}
對于NEVER傳播的事務邏輯,如果存在活動事務,Spring将抛出異常。僞代碼:
if (isExistingTransaction()) {
throw IllegalTransactionStateException;
}
return emptyTransaction;
- NOT_SUPPORTED
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) {
// ...
}
如果目前事務存在,則Spring先将其挂起,然後在沒有事務的情況下執行業務邏輯。
- REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) {
// ...
}
當傳播為REQUIRES_NEW時,Spring将挂起目前事務(如果存在),然後建立一個新事務。僞代碼:
if (isExistingTransaction()) {
suspend(existing);
try {
return createNewTransaction();
} catch (exception) {
resumeAfterBeginException();
throw exception;
}
}
return createNewTransaction();
- NESTED
@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) {
// ...
}
對于NESTED傳播,Spring檢查事務是否存在,如果存在,則标記儲存點。這意味着如果業務邏輯執行抛出異常,那麼事務将復原到此儲存點。如果沒有活動事務,則工作方式與REQUIRED類似。
事務隔離
事務隔離是ACID屬性之一:原子性、一緻性、隔離性和持久性。隔離描述了并發事務所應用的更改如何彼此可見。
每個隔離級别都可以防止對事務産生零或多個并發副作用。
- 髒讀:讀取并發事務的未送出更改
- 不可重複讀取:如果并發事務更新同一行并送出,則在重新讀取行時獲得不同的值
- 幻讀:如果另一個事務添加或删除範圍中的某些行并送出,則在重新執行範圍查詢後擷取不同的行
@Transactional::isolation設定事務的隔離級别有以下五個枚舉:DEFAULT、READ_UNCOMMITTED、READ_COMMITED、REPEATABLE_READ、SERIALIZABLE。
- Spring隔離管理
預設隔離級别為DEFAULT。是以,當Spring建立新事務時,隔離級别将是RDBMS的預設隔離級别。是以,如果此時更改資料庫,需要小心。
當調用具有不同隔離度的方法鍊時,也應該考慮這些情況。在正常流程中,隔離僅在建立新事務時适用。是以,如果出于任何原因,我們不想讓一個方法以不同的隔離方式執行,必須将TransactionManager::setValidateExistingTransaction設定為true。
事務驗證僞代碼如下:
if (isolationLevel != ISOLATION_DEFAULT) {
if (currentTransactionIsolationLevel() != isolationLevel) {
throw IllegalTransactionStateException
}
}
- READ_UNCOMMITED
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
// ...
}
READ_UNCOMMITTED是最低的隔離級别,允許最多的并發通路。
是以,它受到了上述三種并發副作用的影響。具有此隔離的事務讀取其他并發事務的未送出資料。此外,不可重複讀和幻讀都可能發生,在重新讀取行或重新執行範圍查詢時可能獲得不同的結果。
Postgres不支援READ_UNCOMMITTED隔離,而是傳回到READ_COMMITED。此外,Oracle不支援或不允許READ_UNCOMMITTED。
- READ_COMMITED
@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
// ...
}
第二級别隔離READ_COMMITTED防止髒讀。其他并發副作用仍然可能發生。
是以,并發事務中未送出的更改對我們沒有影響,但如果事務送出了其更改,結果可能會通過重新查詢而改變。
READ_COMMITED是Postgres、SQL Server和Oracle的預設級别。
- REPEATABLE_READ
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void log(String message){
// ...
}
第三級别隔離REPEATABLE_READ可防止髒讀和不可重複讀。
是以,我們不受并發事務中未送出的更改的影響。當我們重新查詢一行時,我們不會得到不同的結果。然而,在重新執行範圍查詢時,我們可能會得到新添加或删除的行。
此外,這是防止丢失更新所需的最低級别。當兩個或多個并發事務讀取并更新同一行時,會發生丢失的更新。REPEATABLE_READ根本不允許同時通路一行。是以,丢失的更新不會發生。
REPEATABLE_READ是Mysql中的預設級别。Oracle不支援REPEATABLE_READ。
- SERIALIZABLE
@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
// ...
}
SERIALIZABLE是最進階别的隔離。它可以防止所有提到的并發副作用,但可以導緻最低的并發通路率,因為是按順序執行并發調用。
結論
本文詳細探讨了@Transaction的傳播特性,并發副作用和隔離級别。一般情況下使用預設事務屬性即可,具體配置需要從實際的業務場景涉及的并發事務場景去考慮。