Spring事務隔離級別和傳播機制


引言

什么是事務?

在理解事務之前,我們要先了解事務的基本作用
比如在生活中有這樣一個場景————取錢,每個人應該都干過的事
在ATM機上取錢,1.輸入密碼————2.輸入金額————3.銀行扣錢————4.ATM出錢
以上幾個步驟中,3和4就是必須是一個事務,因為它們之間,要么都完成,要么都不完成
事務其實就是用來解決這種類似的問題
想象一理,如果第3步成功了,第4步失敗(報錯)了,這將會導致非常嚴重的后果,對於普通人而言,錢被扣了,但ATM機沒出錢,肯定會發飆的吧。哈哈
所以第3步和第4步,必須要全部被執行完成,這樣才能保證最后數據的一致性。
對應到業務開發過程中,第3步和第4步,我們把它認為是一個執行單元,但在代碼里面它是一條條的代碼,所以必須要有一種機制能夠保證它們要么全部執行成功,要么全部執行失敗。
在企業級應用程序開發中,事務管理是必不可少的技術,用來確保數據的完整性和一致性。
Spring事務是建立在持久層的基礎之上的,要理解Spring事務,就必須要先了解事務的相關特性

事務特性

1. 原子性(Atomicity)

事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要么全部完成,要么完全不起作用。

2. 一致性(Consistency)

一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的數據不應該被破壞。

3. 隔離性(Isolation)

可能有許多事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。

4. 持久性(Durability)

一旦事務完成,無論發生什么系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化存儲器中。

Mysql事務實現

1.獲取連接
2.關閉自動提交功能(SET AUTOCOMMIT = FALSE)
3.開啟事務
4.業務SQL語句
5.提交事務
6.打開自動提交功能(SET AUTOCOMMIT = TRUE)
7.關閉連接

Mysql(InnoDB)實現

源碼

Spring事務實現原理

Spring支持編程式事務管理以及聲明式事務管理兩種方式。

1. 編程式事務管理

編程式事務管理是侵入性事務管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,對於編程式事務管理,Spring推薦使用TransactionTemplate。

2. 聲明式事務管理

聲明式事務管理建立在AOP之上,其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,執行完目標方法之后根據執行的情況提交或者回滾。
Spring AOP請閱讀筆者另一篇———Spring源碼——AOP實現原理
編程式事務每次實現都要單獨實現,但業務量大功能復雜時,使用編程式事務無疑是痛苦的,而聲明式事務不同,聲明式事務屬於無侵入式,不會影響業務邏輯的實現,只需要在配置文件中做相關的事務規則聲明或者通過注解的方式,便可以將事務規則應用到業務邏輯中
顯然聲明式事務管理要優於編程式事務管理,這正是Spring倡導的非侵入式的編程方式。唯一不足的地方就是聲明式事務管理的粒度是方法級別,而編程式事務管理是可以到代碼塊的,但是可以通過提取方法的方式完成聲明式事務管理的配置。
本文重點講解聲明式事務
在Spring中要使用事務,在注解啟動Spring的情況下,需要使用到@EnableTransactionManagement注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

TransactionManagementConfigurationSelector類屬性ImportSelector(關於@Import注解的使用參考另外一篇Spring擴展——Import注解

TransactionManagementConfigurationSelector源碼內容

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

	/**
	 * 此處是AdviceMode的作用,默認是用代理,另外一個是ASPECTJ
	 *
	 * Returns {@link ProxyTransactionManagementConfiguration} or
	 * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY}
	 * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()},
	 * respectively.
	 */
	@Override
	protected String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] {AutoProxyRegistrar.class.getName(),
						ProxyTransactionManagementConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {determineTransactionAspectClass()};
			default:
				return null;
		}
	}
	private String determineTransactionAspectClass() {
		return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
				TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
				TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
	}
}

通過以上源碼我們發現,這個類實際上是引入了兩個BeanDefinition,這兩個類的Class Type 是AutoProxyRegistrar 和 ProxyTransactionManagementConfiguration

AutoProxyRegistrar


AutoProxyRegistrar類是ImportBeanDefinitionRegistrar的實現類,它會在ConfigurationClassPostProcessor對象執行postProcessBeanDefinitionRegistry方法的時候,執行到AutoProxyRegistrar里面的實現方法registerBeanDefinitions
在AutoProxyRegistrar的registerBeanDefinitions方法里面又向容器中注冊了幾個類

/**
	 * 注冊AOP處理器InfrastructureAdvisorAutoProxyCreator
	 *
	 * Register, escalate, and configure the standard auto proxy creator (APC) against the
	 * given registry. Works by finding the nearest annotation declared on the importing
	 * {@code @Configuration} class that has both {@code mode} and {@code proxyTargetClass}
	 * attributes. If {@code mode} is set to {@code PROXY}, the APC is registered; if
	 * {@code proxyTargetClass} is set to {@code true}, then the APC is forced to use
	 * subclass (CGLIB) proxying.
	 * <p>Several {@code @Enable*} annotations expose both {@code mode} and
	 * {@code proxyTargetClass} attributes. It is important to note that most of these
	 * capabilities end up sharing a {@linkplain AopConfigUtils#AUTO_PROXY_CREATOR_BEAN_NAME
	 * single APC}. For this reason, this implementation doesn't "care" exactly which
	 * annotation it finds -- as long as it exposes the right {@code mode} and
	 * {@code proxyTargetClass} attributes, the APC can be registered and configured all
	 * the same.
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
		// 遍歷所有注解,找到有mode和proxyTargetClass的注解
		for (String annType : annTypes) {
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
			if (candidate == null) {
				continue;
			}
			Object mode = candidate.get("mode");
			Object proxyTargetClass = candidate.get("proxyTargetClass");
			if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
					Boolean.class == proxyTargetClass.getClass()) {
				candidateFound = true;
				if (mode == AdviceMode.PROXY) {
					// 注冊aop
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					// 強制設置proxyTargetClass=true后面使用cglib
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
		if (!candidateFound && logger.isInfoEnabled()) {
			String name = getClass().getSimpleName();
			logger.info(String.format("%s was imported but no annotations were found " +
					"having both 'mode' and 'proxyTargetClass' attributes of type " +
					"AdviceMode and boolean respectively. This means that auto proxy " +
					"creator registration and configuration may not have occurred as " +
					"intended, and components may not be proxied as expected. Check to " +
					"ensure that %s has been @Import'ed on the same class where these " +
					"annotations are declared; otherwise remove the import of %s " +
					"altogether.", name, name, name));
		}
	}

剛剛前面我們談到Spring事務也是基於AOP的擴展,所以這里也必須要引入注冊AOP處理器InfrastructureAdvisorAutoProxyCreator,而它就是生成動態代理對象的重要處理器

我們看到它是屬於AbstractAdvisorAutoProxyCreator的子類,這里跟普通AOP為對象創建代理是一樣的

ProxyTransactionManagementConfiguration

其源碼如下

/**
 * 代理事務配置,注冊事務需要用的一些類,而且Role=ROLE_INFRASTRUCTURE都是屬於內部級別的
 *
 * {@code @Configuration} class that registers the Spring infrastructure beans
 * necessary to enable proxy-based annotation-driven transaction management.
 *
 * @author Chris Beams
 * @author Sebastien Deleuze
 * @since 3.1
 * @see EnableTransactionManagement
 * @see TransactionManagementConfigurationSelector
 */
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

	@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
			TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {

		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
		advisor.setTransactionAttributeSource(transactionAttributeSource);
		advisor.setAdvice(transactionInterceptor);
		if (this.enableTx != null) {
			advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
		}
		return advisor;
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource() {
		return new AnnotationTransactionAttributeSource();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
		TransactionInterceptor interceptor = new TransactionInterceptor();
		interceptor.setTransactionAttributeSource(transactionAttributeSource);
		if (this.txManager != null) {
			interceptor.setTransactionManager(this.txManager);
		}
		return interceptor;
	}
}

這個配置類的主要功能是用來創建用來處理事務的Advisor,而Advisor又是由Advice和Pointcut組成的

Advisor

在Spring事務中,Advisor的實現類是BeanFactoryTransactionAttributeSourceAdvisor

**
 * 事務屬性通知器,存放事務注解的方法相關的屬性,本身屬於一個Advisor
 *
 * Advisor driven by a {@link TransactionAttributeSource}, used to include
 * a transaction advice bean for methods that are transactional.
 *
 * @author Juergen Hoeller
 * @since 2.5.5
 * @see #setAdviceBeanName
 * @see TransactionInterceptor
 * @see TransactionAttributeSourceAdvisor
 */
@SuppressWarnings("serial")
public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
	@Nullable
	private TransactionAttributeSource transactionAttributeSource;
	/**
	 * 內部默認實現了Pointcut
	 */
	private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
		@Override
		@Nullable
		protected TransactionAttributeSource getTransactionAttributeSource() {
			return transactionAttributeSource;
		}
	};
	/**
	 * Set the transaction attribute source which is used to find transaction
	 * attributes. This should usually be identical to the source reference
	 * set on the transaction interceptor itself.
	 * @see TransactionInterceptor#setTransactionAttributeSource
	 */
	public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
		this.transactionAttributeSource = transactionAttributeSource;
	}
	/**
	 * Set the {@link ClassFilter} to use for this pointcut.
	 * Default is {@link ClassFilter#TRUE}.
	 */
	public void setClassFilter(ClassFilter classFilter) {
		this.pointcut.setClassFilter(classFilter);
	}
	@Override
	public Pointcut getPointcut() {
		return this.pointcut;
	}
}

Advice

Spring事務中的Advice就是配置類中@Bean注解了的TransactionInterceptor

Pointcut

在BeanFactoryTransactionAttributeSourceAdvisor類中,有一個默認的屬性pointcut。

這樣,用於實現Spring事務的Advisor,Pointcut以及Advice都已經找到了。關於這三個類的具體作用,我們這里進行整體的上的講解,后面我們將會深入其內部講解其是如何進行bean的過濾以及事務邏輯的織入的。

BeanFactoryTransactionAttributeSourceAdvisor
封裝了實現事務所需的所有屬性,包括Pointcut,Advice,TransactionManager以及一些其他的在Transactional注解中聲明的屬性;
TransactionAttributeSourcePointcut
用於判斷哪些bean需要織入當前的事務邏輯。這里可想而知,其判斷的基本邏輯就是判斷其方法或類聲明上有沒有使用@Transactional注解,如果使用了就是需要織入事務邏輯的bean;
TransactionInterceptor
這個bean本質上是一個Advice,其封裝了當前需要織入目標bean的切面邏輯,也就是Spring事務是如果借助於數據庫事務來實現對目標方法的環繞的。
到此,使用Spring事務的前期准備工作已經做完
在調用的時候,跟AOP一樣,會通過執行鏈的,調用到具體的Advisor,而這里的Advisor就是我們上面找到的BeanFactoryTransactionAttributeSourceAdvisor,然后Advisor在攔截的時候,會去調用Advice里面的invoke方法
TransactionInterceptor里面的invoke方法

	@Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		// 獲取我們的代理對象的class屬性
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
		/**
		 * 以事務的方式調用目標方法
		 * 在這埋了一個鈎子函數 用來回調目標方法的
		 */
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}

而具體的處理邏輯在TransactionInterceptor的父類TransactionAspectSupport中的invokeWithinTransaction方法里面,因為源碼較長,這里就不粘貼過來了
最終事務的處理,是要表現在持久層上,所以這里的處理跟上面mysql數據庫里面直接使用事務的情況一樣

事務隔離級別

要理解隔離級別,必須要先知道在並發的情況下,引起的三種狀況。
1. Dirty Reads 臟讀
一個事務正在對數據進行更新操作,但是更新還未提交,另一個事務這時也來操作這組數據,並且讀取了前一個事務還未提交的數據,而前一個事務如果操作失敗進行了回滾,后一個事務讀取的就是錯誤數據,這樣就造成了臟讀。
2. Non-Repeatable Reads 不可重復讀
一個事務多次讀取同一數據,在該事務還未結束時,另一個事務也對該數據進行了操作,而且在第一個事務兩次次讀取之間,第二個事務對數據進行了更新,那么第一個事務前后兩次讀取到的數據是不同的,這樣就造成了不可重復讀。
3. Phantom Reads 幻像讀
第一個數據正在查詢符合某一條件的數據,這時,另一個事務又插入了一條符合條件的數據,第一個事務在第二次查詢符合同一條件的數據時,發現多了一條前一次查詢時沒有的數據,仿佛幻覺一樣,這就是幻像讀。

非重復度和幻像讀的區別
非重復讀是指同一查詢在同一事務中多次進行,由於其他提交事務所做的修改或刪除,每次返回不同的結果集,此時發生非重復讀。
幻像讀是指同一查詢在同一事務中多次進行,由於其他提交事務所做的插入操作,每次返回不同的結果集,此時發生幻像讀。
表面上看,區別就在於非重復讀能看見其他事務提交的修改和刪除,而幻像能看見其他事務提交的插入。
歸納

名稱 數據狀態 實際行為 產生原因
臟讀 未提交 打算提交但是數據回滾了,讀取了提交的數據 數據的讀取
不可重復讀 已提交 讀取了修改前的數據 數據的修改
幻讀 已提交 讀取了插入前的數據 數據的插入

READ_UNCOMMITTED (讀未提交)

這是事務最低的隔離級別,它允許另外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生臟讀,不可重復讀和幻讀

READ_COMMITTED (讀已提交)

保證一個事務修改的數據提交后才能被另外一個事務讀取,另外一個事務不能讀取該事務未提交的數據。這種事務隔離級別可以避免臟讀出現,但是可能會出現不可重復讀和幻像讀

REPEATABLE_READ (可重復讀)

這種事務隔離級別可以防止臟讀、不可重復讀,但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了不可重復讀。

SERIALIZABLE(串行化)

這是花費最高代價但是最可靠的事務隔離級別,事務被處理為順序執行。除了防止臟讀、不可重復讀外,還避免了幻像讀
歸納

名稱 結果 臟讀 不可重復讀 幻讀
Read UnCommitted(讀未提交) 什么都不解決
Read Committed(讀提交) 解決了臟讀的問題 -
Repeatable Read(重復讀) (mysql的默認級別)解決了不可重復讀 ) - -
Serializable(序列化) 解決所有問題 - - -

Spring事務傳播特性

比如 A 方法開啟了事務,A 方法中調用了 B 方法,B 方法也開啟了事務,那么這時候 A 和 B 之間的事務是獨立進行的還是一起作用的?
Spring 在這方面為我們提供了管理的方法,也就是下面我們要討論的 Spring 的事務傳播機制。

七種傳播特性

PROPAGATION_REQUIRED

默認的spring事務傳播級別,使用該級別的特點是,如果上下文中已經存在事務,那么就加入到事務中執行,如果當前上下文中不存在事務,則新建事務執行。所以這個級別通常能滿足處理大多數的業務場景。

PROPAGATION_SUPPORTS

從字面意思就知道,supports,支持,該傳播級別的特點是,如果上下文存在事務,則支持事務加入事務,如果沒有事務,則使用非事務的方式執行。所以說,並非所有的包在transactionTemplate.execute中的代碼都會有事務支持。這個通常是用來處理那些並非原子性的非核心業務邏輯操作。應用場景較少。

PROPAGATION_MANDATORY

該級別的事務要求上下文中必須要存在事務,否則就會拋出異常!配置該方式的傳播級別是有效的控制上下文調用代碼遺漏添加事務控制的保證手段。比如一段代碼不能單獨被調用執行,但是一旦被調用,就必須有事務包含的情況,就可以使用這個傳播級別。

PROPAGATION_REQUIRES_NEW

從字面即可知道,new,每次都要一個新事務,該傳播級別的特點是,每次都會新建一個事務,並且同時將上下文中的事務掛起,執行當前新建事務完成以后,上下文事務恢復再執行。
這是一個很有用的傳播級別,舉一個應用場景:現在有一個發送100個紅包的操作,在發送之前,要做一些系統的初始化、驗證、數據記錄操作,然后發送100封紅包,然后再記錄發送日志,發送日志要求100%的准確,如果日志不准確,那么整個父事務邏輯需要回滾。
怎么處理整個業務需求呢?就是通過這個PROPAGATION_REQUIRES_NEW 級別的事務傳播控制就可以完成。發送紅包的子事務不會直接影響到父事務的提交和回滾。

PROPAGATION_NOT_SUPPORTED

這個也可以從字面得知,not supported ,不支持,當前級別的特點就是上下文中存在事務,則掛起事務,執行當前邏輯,結束后恢復上下文的事務。
這個級別有什么好處?可以幫助你將事務極可能的縮小。我們知道一個事務越大,它存在的風險也就越多。所以在處理事務的過程中,要保證盡可能的縮小范圍。比如一段代碼,是每次邏輯操作都必須調用的,比如循環1000次的某個非核心業務邏輯操作。這樣的代碼如果包在事務中,勢必造成事務太大,導致出現一些難以考慮周全的異常情況。所以這個事務這個級別的傳播級別就派上用場了。用當前級別的事務模板抱起來就可以了。

PROPAGATION_NEVER

該事務更嚴格,上面一個事務傳播級別只是不支持而已,有事務就掛起,而PROPAGATION_NEVER傳播級別要求上下文中不能存在事務,一旦有事務,就拋出runtime異常,強制停止執行!這個級別上輩子跟事務有仇。

PROPAGATION_NESTED

字面也可知道,nested,嵌套級別事務。該傳播級別特征是,如果上下文中存在事務,則嵌套事務執行,如果不存在事務,則新建事務。

兩個概念

嵌套事務

那么什么是嵌套事務呢?很多人都不理解,我看過一些博客,都是有些理解偏差
嵌套是子事務套在父事務中執行,子事務是父事務的一部分,在進入子事務之前,父事務建立一個回滾點,叫save point,然后執行子事務,這個子事務的執行也算是父事務的一部分,然后子事務執行結束,父事務繼續執行。重點就在於那個save point。看幾個問題就明了了:
如果子事務回滾,會發生什么
父事務會回滾到進入子事務前建立的save point,然后嘗試其他的事務或者其他的業務邏輯,父事務之前的操作不會受到影響,更不會自動回滾。
如果父事務回滾,會發生什么
父事務回滾,子事務也會跟着回滾!為什么呢,因為父事務結束之前,子事務是不會提交的,我們說子事務是父事務的一部分,正是這個道理。那么:
事務的提交,是什么情況
是父事務先提交,然后子事務提交,還是子事務先提交,父事務再提交?答案是第二種情況,還是那句話,子事務是父事務的一部分,由父事務統一提交。
現在你再體會一下這個”嵌套“,是不是有那么點意思?

事務掛起

實際上Spring在實現的時候,是通過對應的bind 和 unbind 操作,是在ThreadLocal 對象里,將資源對象綁定或移出當前線程對應的 resources 來實現的。
NESTED 的特性,本質上還是同一個事務的不同保存點,如果涉及到外層事務回滾,則內層的也將會被回滾;
REQUIRED_NEW 的實現對應的是一個新的事務,拿到的是新的資源,所以外層事務回滾時,不影響內層事務。
以上是事務的7個傳播級別,在日常應用中,通常可以滿足各種業務需求,但是除了傳播級別,在讀取數據庫的過程中,如果兩個事務並發執行,那么彼此之間的數據是如何影響的呢?————請看上面的隔離級別


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM