您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么理解Spring事務”,在日常操作中,相信很多人在怎么理解Spring事務問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么理解Spring事務”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
1、Spring的事務和數據庫的事務隔離是一個概念么?
先來第一個問題,Spring的事務隔離級別和數據的事務隔離級別,是一回事么?
其實,數據庫一般只有4種隔離機制,Spring抽象出一種default,根據數據設置來變動。
read uncommitted(未提交讀)
read committed(提交讀、不可重復讀)
repeatable read(可重復讀)
serializable(可串行化)
default (PlatformTransactionManager默認的隔離級別,使用的就是數據庫默認的)
這是因為,Spring只提供統一事務管理接口,具體實現都是由各數據庫自己實現(如MySQL)。Spring會在事務開始時,根據當前環境中設置的隔離級別,調整數據庫隔離級別,由此保持一致。
在DataSourceUtils文件中,代碼詳細的輸出了這個過程。
// Apply specific isolation level, if any. Integer previousIsolationLevel = null; if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { if (logger.isDebugEnabled()) { logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " + definition.getIsolationLevel()); } int currentIsolation = con.getTransactionIsolation(); if (currentIsolation != definition.getIsolationLevel()) { previousIsolationLevel = currentIsolation; con.setTransactionIsolation(definition.getIsolationLevel()); } }
結論:三種情況,如果Spring沒有指定事務隔離級別,則會采用數據庫默認的事務隔離級別;當Spring指定了事務隔離級別,則會在代碼里將事務隔離級別修改為指定值;當數據庫不支持這種隔離級別,效果則以數據庫的為準(比如采用了MyISAM引擎)。
我們會使用如下的方式進行聲明。如果不是有性能和需求問題,就不要瞎改。事務處理弄不好是會鎖表的,而鎖表在大并發的情況下是會死人的。
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
2、Spring事務的7種傳播機制
只要寫代碼,代碼總會存在嵌套,或者循環,造成了事務的嵌套或者循環。那么事務在這些情況下,根據配置會有不同的反應。
REQUIRED 這是默認的。表示當前方法必須在一個具有事務的上下文中運行,如有客戶端有事務在進行,那么被調用端將在該事務中運行,否則的話重新開啟一個事務。(如果被調用端發生異常,那么調用端和被調用端事務都將回滾)
REQUIRE_NEW 表示當前方法必須運行在它自己的事務中。如果存在當前事務,在該方法執行期間,當前事務會被掛起
NESTED 如果當前方法正有一個事務在運行中,則該方法應該運行在一個嵌套事務中,被嵌套的事務可以獨立于被封裝的事務中進行提交或者回滾。如果封裝事務存在,并且外層事務拋出異常回滾,那么內層事務必須回滾,反之,內層事務并不影響外層事務。如果封裝事務不存在,則同required的一樣
SUPPORTS 表示當前方法不必需要具有一個事務上下文,但是如果有一個事務的話,它也可以在這個事務中運行
NOT_SUPPORTED 表示該方法不應該在一個事務中運行。如果有一個事務正在運行,他將在運行期被掛起,直到這個事務提交或者回滾才恢復執行
MANDATORY 表示當前方法必須在一個事務中運行,如果沒有事務,將拋出異常
NEVER 表示當方法務不應該在一個事務中運行,如果存在一個事務,則拋出異常
一般用得比較多的是REQUIRED , REQUIRES_NEW,用到其他的,你就要小心了,搞懂再用。
最怕如果這倆字了,它會將事情搞的很復雜,尤其是代碼量大的時候,你永遠不知道你寫的service會被誰用到。這就很尷尬了。
我們會采用下面的方式進行聲明。鑒于Spring的事務傳播非常的繞,如果功能滿足需求,那么就用默認的就好,否則會引起不必要的麻煩。
@Transactional(propagation=Propagation.REQUIRED)
3、事務傳播機制是怎么實現的?
事務傳播機制看似神奇,實際上是使用簡單的ThreadLocal的機制實現的。所以,如果調用的方法是在新線程調用的,事務傳播實際上是會失效的。這不同于我們以前講到的透傳,Spring并沒有做這樣的處理。
所以事務傳播機制,只有翻一遍源代碼,才能印象深刻。僅靠文字去傳播,很多東西會變得不可描述。
如圖,PlatformTransactionManager只有簡單的三個抽象接口,定義了包含JDBC在內的Spring所有的事務操作。
我們平常說的JDBC,只是占到其中一部分。
實現的方式,依然是使用AOP來實現的,具體的實現類是由TransactionAspectSupport來實現的。可以看到,代碼定義了一個叫做transactionInfoHolder的ThreadLocal變量,當用到它的時候,就能夠確保在同一個線程下,獲取的變量是一致的。
/** * Holder to support the {@code currentTransactionStatus()} method, * and to support communication between different cooperating advices * (e.g. before and after advice) if the aspect involves more than a * single method (as will be the case for around advice). */ private static final ThreadLocal<TransactionInfo> transactionInfoHolder = new NamedThreadLocal<>("Current aspect-driven transaction");
具體的業務邏輯,是在invokeWithinTransaction中實現的。如果你繼續向下跟蹤的話,會找到AbstractPlatformTransactionManager類中的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) { throw new 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) { throw new IllegalTransactionStateException( "No existing transaction found for transaction marked with propagation 'mandatory'"); } else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { SuspendedResourcesHolder suspendedResources = suspend(null); if (debugEnabled) { logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); } 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); } }
不用我做過多解釋了吧,一切明顯的邏輯,都在代碼里。事務就是在這里創建的。
4、查詢方法可以不開啟事務么?
事務有個readonly,控制了事務的只讀屬性,和事務是否開啟沒半毛錢關系。
在以前的一篇文章中,談到通過設置readonly屬性來控制語句的路由:”MySQL官方驅動“主從分離的神秘面紗(掃盲篇),這其中就用到了事務的其中一個屬性readonly,它最終是體現在數據庫連接層面的。
connection.setReadOnly(true);
在Spring中的使用方式如下:
@Transactional(readOnly=true)
值得注意的是,這個屬性設置之后,并不是每個底層的數據庫都支持。中間層的ORM或者驅動,也可能會拿這個屬性做一些文章,所以與其說這個readonly是功能性的,不如說是一種暗示。
拿MySQL來說,有兩種提交模式:
SET AUTOCOMMIT=0 禁止自動提交
SET AUTOCOMMIT=1 開啟自動提交
這都是實打實的SQL語句,所以如果開啟了事務,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查詢之前,數據被其他用戶改變,就會造成數據的前后不一。
也僅有在這種情況下,要開啟讀事務。
5、private方法加事務注解有用么?
@Transaction注解加在private上,并沒有什么卵用。
這倒不是事務處理的代碼去寫的特性。由于事務的這些功能,是通過AOP方式強加進去的,所以它收到動態代理的控制。
private和final修飾的方法,不會被代理。
但是,你卻可以把private方法放在帶有事務功能的public方法里。這樣,它看起來也有了事務的一些功能特性,但它并沒有。
到此,關于“怎么理解Spring事務”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。