概要
Spring事務基於數據庫事務,JDBC事務過程:
- 獲取連接
Connection con = DriverManager.getConnection()
- 開啓事務
con.setAutoCommit(true/false);
- 執行CRUD
- 提交事務/回滾事務
con.commit()
,con.rollback();
- 關閉連接
conn.close();
Spring事務主要分爲兩種:
- 編程式事務
- 聲明式事務
編程式事務
try {
// something
transactionManager.commit();
} catch (Exception e) {
transactionManager.rollback();
throw new RuntimeException("失敗");
}
顯而易見,代碼侵入性比較強,代碼冗餘。
聲明式事務
使用Spring聲明式事務,可以不再寫步驟2和4的代碼。基於AOP,有兩種實現方式:基於TX和AOP的xml配置文件方式、基於@Transactional註解的形式。配置文件開啓註解驅動,在類和方法上通過註解@Transactional標識。Spring在啓動時會去解析並生成相關的bean,爲這些類和方法生成代理,並根據@Transaction的相關參數進行相關配置注入,這樣就在代理中把相關的事務處理掉(開啓正常提交事務,異常回滾事務)。真正的數據庫層的事務提交和回滾是通過binlog或redo log實現。
Transactional
@Transactional可作用在接口、類、類方法。
- 類:表示該類所有的public方法都配置相同的事務屬性信息
- 方法:方法的事務優先級更高,會覆蓋類的事務配置信息
- 接口:不推薦這種使用方法,因爲一旦標註在Interface上並且配置Spring AOP使用CGLib動態代理,將會導致@Transactional註解失效
屬性
@Transactional註解有哪些屬性?
- propagation,事務的傳播行爲,包括7種枚舉值;
- isolation,事務的隔離級別,枚舉值有5個,Isolation.DEFAULT:默認值,即使用底層數據庫的隔離級別,其他四種即爲MySQL的四種隔離級別。源碼在DataSourceUtils:
// Apply specific isolation level, if any.
Integer previousIsolationLevel = null;
if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
int currentIsolation = con.getTransactionIsolation();
if (currentIsolation != definition.getIsolationLevel()) {
previousIsolationLevel = currentIsolation;
con.setTransactionIsolation(definition.getIsolationLevel());
}
}
- timeout,事務超時時間,默認值爲 -1。如果超過該時間限制但事務還沒有完成,則自動回滾事務
- readOnly,指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 爲 true
- rollbackFor,指定觸發事務回滾的異常類型,可指定多個異常類型
- noRollbackFor,指定不觸發事務回滾的異常類型,可指定多個異常類型
- transactionManager,事務管理器,用於配置有多數據源,即多事務管理器情況下具體指定某一個事務管理器
事務傳播性
事務的傳播性一般在事務嵌套時候使用,比如在事務A裏面調用另外一個使用事務的方法,那麼這倆個事務是各自作爲獨立的事務執行提交,還是內層的事務合併到外層的事務一塊提交那,這就是事務傳播性要確定的問題。
spring事務的傳播屬性,就是多個事務同時存在時,spring應該如何處理這些事務的行爲。亦多個事務方法相互調用時,事務如何在這些方法間傳播。這些屬性在TransactionDefinition中定義:
- Propagation.REQUIRED:默認值。如果當前存在事務,則加入該事務,如果不存在,則創建一個新的事務。(也就是說如果A方法和B方法都添加註解,在默認傳播模式下,A方法內部調用B方法,會把兩個方法的事務合併爲一個事務 )
- Propagation.SUPPORTS:如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行
- Propagation.MANDATORY:如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常
- Propagation.REQUIRES_NEW:重新創建一個新的事務,如果當前存在事務,暫停當前的事務。新建的事務將和被掛起的事務沒有任何關係,是兩個獨立的事務,外層事務失敗回滾之後,不能回滾內層事務執行的結果,內層事務失敗拋出異常,外層事務捕獲,也可以不處理回滾操作。(當類A中的a方法用默認Propagation.REQUIRED模式,類B中的b方法加上採用Propagation.REQUIRES_NEW模式,然後在a方法中調用b方法操作數據庫,然而a方法拋出異常後,b方法並沒有進行回滾,因爲Propagation.REQUIRES_NEW會暫停a方法的事務)
- Propagation.NOT_SUPPORTED:以非事務的方式運行,如果當前存在事務,暫停當前的事務
- Propagation.NEVER:以非事務的方式運行,如果當前存在事務,則拋出異常
- Propagation.NESTED :如果一個活動的事務存在,則運行在一個嵌套的事務中。如果沒有活動事務,則按REQUIRED屬性執行。它使用一個單獨的事務,這個事務擁有多個可以回滾的保存點。內部事務的回滾不會對外部事務造成影響。它只對DataSourceTransactionManager事務管理器起效
readOnly
指定事務是否爲只讀事務,默認值爲 false。設置爲TRUE,需要底層數據庫支持。
MySQL來說,有兩種提交模式:
SET AUTOCOMMIT=0
:禁止自動提交SET AUTOCOMMIT=1
:開啓自動提交
若開啓事務,AUTOCOMMIT要爲false,Spring源碼處理:
con.setAutoCommit(false);
// 只讀事務
if (isEnforceReadOnly() && definition.isReadOnly()) {
try (Statement stmt = con.createStatement()) {
stmt.executeUpdate("SET TRANSACTION READ ONLY");
}
}
不加Transaction註解,默認是不開啓事務。單條查詢語句也沒有必要開啓事務,數據庫默認的配置就能滿足需求。但一次執行多條查詢語句,如統計查詢,報表查詢;此時多條查詢SQL必須保證整體的讀一致性,否則,在前條SQL查詢之後,後條SQL查詢之前,數據被其他用戶改變,就會造成數據的前後不一。需要開啓讀事務。
不生效
- 底層數據庫引擎不支持事務
若數據庫引擎不支持事務,則Spring自然無法支持事務 - 當Spring開啓事務並設置傳播機制,覆蓋MySQL已有的事務隔離級別。如果MySQL不支持該隔離級別,Spring的事務就也不會生效。和第一點比較類似,但是不盡相同。三種配置將會導致事務失效:
Propagation.SUPPORTS
Propagation.NOT_SUPPORTED
Propagation.NEVER
- 非public的方法
在非public方法上標記@Transactional,不報錯(可以通過編譯,但是IDE有警告),但沒有事務功能。因爲Transactional事務基於AOP,動態代理只能針對public方法進行代理。源碼如下:
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
}
- 異常被catch
在整個事務的方法中使用try-catch,導致異常無法拋出,自然會導致事務失效。
@Transactional
public void method() {
try {
// transaction.commit();
} catch (Exception ex) {
return;
}
}
- rollbackFor屬性設置錯誤
指定異常觸發回滾,但是設置錯誤導致一些異常不能觸發回滾 - noRollbackFor屬性設置錯誤
和rollbackFor類似。 - 方法中調用同類的方法
最複雜的情況。
一個類中的A方法(無註解Transactional)在內部調用B方法(有註解Transactional),這樣會導致B方法中的事務失效。
失效原因:Spring在掃描Bean時會自動爲標註@Transactional註解的類生成一個代理類,當有註解的方法被調用的時候,實際上是代理類調用的,代理類在調用之前會開啓事務,執行事務的操作,但是同類中的方法互相調用,相當於this.B()
,此時的B方法並非是代理類調用,而是直接通過原有的Bean直接調用,所以註解會失效。
a1方法是目標類A的原生方法,調用a1的時候即直接進入目標類A進行調用,在目標類A裏面只有a2的原生方法,在a1裏調用a2,即直接執行a2的原生方法,並不通過創建代理對象進行調用,所以並不會進入TransactionInterceptor的invoke方法,不會開啓事務。
原理
PlatformTransactionManager,基於AOP的類TransactionAspectSupport:
核心屬性:
private static final ThreadLocal<TransactionInfo> transactionInfoHolder = new NamedThreadLocal<>("Current aspect-driven transaction");
核心方法invokeWithinTransaction():
getTransaction():
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
thrownew InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
thrownew IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
elseif (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
try {
// 核心
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
事務攔截器
PlatformTransactionManager是Spring 中的事務管理接口,真正定義事務如何回滾和提交。
TransactionInterceptor,負責攔截方法執行,判斷是否需要提交或者回滾事務。
http://blog.leanote.com/post/medusar/Spring%E4%BA%8B%E5%8A%A1
https://www.iteye.com/topic/1122740