Spring原始碼分析(八)深入瞭解事務管理的流程

清幽之地發表於2019-03-03

一、前言

事務管理對於系統應用來說至關重要,它保證了資料的完整性和安全性。特別是針對金融服務而言,更是不可或缺。經典的場景是轉賬操作,A賬戶向B賬戶轉賬5000元,首先A餘額減少5000元,然後B餘額增加5000元。通常情況下,都能正常完成交易。但也難免會遇到故障,這時候不能出現A的餘額減少了,B的餘額卻沒有增加的情況。

在分析原始碼之前,我們先來了解下Spring中的一些事務屬性。

二、事務屬性

1、事務隔離級別

事務隔離級別,定義了一個事務可能受其他併發事務活動影響的程度。

在應用程式中,多個事務同時執行,經常會為了完成相同的工作而操作同一資料。併發是必然的,但可能會導致以下問題。

  • 髒讀(Dirty read)。有T1、T2兩個事務,T1改寫了資料但尚未提交,T2卻可以讀取到改寫後的資料。

  • 不可重複讀(Nonrepeatable read)。有T1、T2兩個事務。T1多次執行相同的查詢,但得到的結果卻不相同。通常是因為T2在T1查詢期間對資料做了更新。

  • 幻讀(Phantom reads)。有T1、T2兩個事務。當T1正在讀取記錄時,T2併發插入了記錄,幻讀發生了。其實跟不可重複讀類似。

理想狀態下,所有的事務都應該隔離,從而防止以上情況出現。然而,完全將事務隔離,將大大降低效能。因為隔離要鎖定資料行或者資料表,會阻礙併發,要求事務相互等待來完成工作。所以,就區分了幾種隔離級別,來靈活應對不同場景下的資料要求。

隔離級別 含義
DEFAULT 這是Spring中的事務隔離級別預設值。它代表使用底層資料庫的預設隔離級別。MySQL預設是“可重複讀”,Oracle預設是“提交讀”。
READ_UNCOMMITTED 未提交讀,允許讀取到尚未提交的資料。會導致髒讀、不可重複讀和幻讀。
READ_COMMITTED 已提交讀,允許讀取到已經提交的事務資料。可以防止髒讀,但仍會出現不可重複讀和幻讀。
REPEATABLE READ 可重複讀,對相同欄位的多次讀取的結果是一致的,除非資料被當前事務本身改變。可以避免髒讀和不可重複讀,但幻讀仍會發生。
SERIALIZABLE 序列化,所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾。事實上,基本不會使用到這個級別。

2、事務傳播行為

所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。Spring中定義7種傳播行為。

傳播行為 含義
PROPAGATION_MANDATORY 表示該方法必須在一個事務中執行。如果當前沒有事務,則丟擲異常。
PROPAGATION_NEVER 表示該方法不應當在一個事務中執行。如果一個事務正在執行,則丟擲異常。
PROPAGATION_NOT_SUPPORTED 表示該方法不應該在一個事務中執行。如果一個現有事務正在進行中,它將在該方法的執行期間被掛起。
PROPAGATION_SUPPORTS 表示當前方法不需要事務性上下文,但是如果有一個事務已經在執行的話,它也可以在這個事務裡執行。
PROPAGATION_REQUIRES_NEW 表示當前方法必須在它自己的事務裡執行。一個新的事務將被啟動,而且如果有一個現有事務在執行的話,則將在這個方法執行期間被掛起。
PROPAGATION_REQUIRES 表示當前方法必須在一個事務中執行。如果一個現有事務正在進行中,該方法將在那個事務中執行,否則就要開始一個新事務。
PROPAGATION_NESTED 如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於PROPAGATION_REQUIRED。

3、事務超時

所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。

4、只讀屬性

事務的只讀屬性是指,對事務性資源進行只讀操作或者是讀寫操作。如果一個事務只對後端資料庫執行讀操作,那麼該資料庫就可能利用那個事務的只讀特性,採取某些優化 措施。通過把一個事務宣告為只讀,可以給後端資料庫一個機會來應用那些它認為合適的優化措施。

5、回滾規則

通常情況下,如果在事務中丟擲了未檢查異常(繼承自 RuntimeException 的異常),則預設將回滾事務。如果沒有丟擲任何異常,或者丟擲了已檢查異常,則仍然提交事務。這通常也是大多數開發者希望的處理方式,也是 EJB 中的預設處理方式。但是,我們可以根據需要人為控制事務在丟擲某些未檢查異常時任然提交事務,或者在丟擲某些已檢查異常時回滾事務。

三、Spring事務的三大介面

1、 PlatformTransactionManager

PlatformTransactionManager是事務管理的抽象層,Spring根據這個抽象層提供許多不同的具體實現。比如DataSourceTransactionManager、JpaTransactionManager、HibernateTransactionManager等。

public interface PlatformTransactionManager {
	
	//返回當前活動的事務或建立一個新的事務。
	//引數definition描述了事務的屬性,比如傳播行為,隔離級別,超時等
	TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException
	//根據給定事務的狀態提交給定事務
	void commit(TransactionStatus status) throws TransactionException;	
	//執行給定事務的回滾
	void rollback(TransactionStatus status) throws TransactionException;
}
複製程式碼

2、 TransactionDefinition

定義了事務屬性

public interface TransactionDefinition {
	
	//事務的7個傳播行為
	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;

	//事務的5個隔離級別
	int ISOLATION_DEFAULT = -1;
	int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
	int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
	int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
	int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

	//事務超時時間
	int TIMEOUT_DEFAULT = -1;
	//返回傳播行為
	int getPropagationBehavior();
	//返回隔離級別
	int getIsolationLevel();
	//返回超時時間
	int getTimeout();
	//是否為只讀事務
	boolean isReadOnly();
	//返回事務的名稱
	String getName();
}
複製程式碼

3、 TransactionStatus

代表當前事務的狀態,也可以對當前事務進行控制。

public interface TransactionStatus extends SavepointManager, Flushable {

	//當前事務狀態是否是新事務
	boolean isNewTransaction();

	//當前事務是否有儲存點
	boolean hasSavepoint();

	//設定當前事務應該回滾,如果設定這個,則commit不起作用
	void setRollbackOnly();

	//當前事務是否應該回滾
	boolean isRollbackOnly();

	//用於重新整理底層會話中的修改到資料庫,一般用於重新整理如Hibernate/JPA的會話,
   //可能對如JDBC型別的事務無任何影響
	void flush();

	//當前事務否已經完成
	boolean isCompleted();

}
複製程式碼

四、事務的實現方式

Spring中的事務實現方式,總體可以分為兩類:程式設計式事務和宣告式事務。

1、程式設計式事務

顧名思義,程式設計式事務就是以程式碼程式設計的方式來控制事務的執行。我們來看一個事例。 PlatformTransactionManager採用Spring JDBC的事務管理器。資料來源是一個資料庫連線池,先看下XML配置。

<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">  
	<property name="driverClassName" value="${jdbc.driverClassName}" />  
	<property name="url" value="${jdbc.url}" />  
	<property name="username" value="${jdbc.username}" />  
	<property name="password" value="${jdbc.password}" />  
</bean>
 
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
	<property name="dataSource" ref="dataSource" />  
</bean>  

<bean id="transactionDefinition" class="org.springframework.transaction.support.DefaultTransactionDefinition">
	<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
複製程式碼

在程式碼中直接通過事務管理器即可控制事務的執行。我們看一個Service中的方法。

public void insertUser(User user) {
	TransactionStatus txStatus = transactionManager.getTransaction(transactionDefinition);
	try {
		System.out.println("----------新增使用者資訊------------");
		transactionManager.commit(txStatus);
	} catch (Exception e) {
		System.out.println("儲存使用者資訊傳送異常,"+e);
		transactionManager.rollback(txStatus);
	}
}
複製程式碼

通過以上方式就完成了以程式設計式事務對業務方法的管理。當然了,它的缺點也很明顯,事務程式碼和業務程式碼糅雜在一起,破壞了業務程式碼條理性,而且也不利於維護和擴充套件。 有沒有更好的實現方法呢?結合我們上一節學習的Spring AOP知識,應該怎麼做呢?

2、宣告式事務

沒錯,利用Spring AOP對方法進行攔截。在方法開始之前建立或者加入一個事務,在方法執行完畢之後根據情況提交或回滾事務。這個就是宣告式事務。我們來看一個基於 名稱空間的宣告式事務管理。

首先,還是先配置一個事務管理器。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
	<property name="dataSource" ref="dataSource" />  
</bean>  
複製程式碼

其次,通過AOP標籤配置一個advisor,它包含一個advice和一個pointcut。

<aop:config>  
	<aop:pointcut id="serviceMethods" 
		expression="execution(* com.viewscenes.netsupervisor.service..*(..))"/>  
	<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>  
</aop:config>  
複製程式碼

最後,通過tx標籤定義一個advice。它本身是一個事務的通知,當前要包含事務的管理器和事務的屬性。

<tx:advice id="txAdvice" transaction-manager="transactionManager">  
	<tx:attributes>  
		<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>  
		<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>  
		<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>  
		<tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>  
		<tx:method name="*"/>  
	</tx:attributes>  
</tx:advice>  
複製程式碼

通過這種方式,我們的業務程式碼不需要新增任何關於事務的程式碼,就可以完成事務的操作。在實際開發中,我們大部分也都是使用這種方式或者通過Annotation的方式來配置事務,而不大可能使用程式設計式事務。

五、原始碼解析

囉嗦了這麼多,是為了先把事務的執行規則、屬性講清楚,不然上來就是原始碼,容易暈車哈。原始碼以tx標籤為例的配置方式進行分析,程式設計式事務和Annotation註解方式的事務本章節暫不涉及。

1、tx標籤的解析

tx標籤的解析,在Spring掃描XML的時候就被載入到了,具體會定位到org.springframework.transaction.config.TxAdviceBeanDefinitionParser類。然後呼叫類的parse()方法,但是我們發現此類並沒有parse方法,往上找最後呼叫到父類的父類AbstractBeanDefinitionParser.parse()。最後返回一個構建完畢的BeanDefinition物件,並註冊到bean容器中,等待下一步的例項化。整個過程我們可以分為三個步驟來看。

  • 1、建立TransactionInterceptor

TransactionInterceptor類就是事務處理的攔截器類,它實現了MethodInterceptor介面。在呼叫代理類的invoke方法時,實際呼叫的就是這個類的invoke。

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
	
	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
	
	//getBeanClass呼叫到子類的方法,這個子類就是TxAdviceBeanDefinitionParser
	//它返回的就是return TransactionInterceptor.class;
	Class<?> beanClass = getBeanClass(element);
	if (beanClass != null) {
		builder.getRawBeanDefinition().setBeanClass(beanClass);
	}
	builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
	//呼叫子類TxAdviceBeanDefinitionParser方法
	doParse(element, parserContext, builder);
	return builder.getBeanDefinition();
}
複製程式碼
  • 2、解析事務屬性

第一步建立了TransactionInterceptor的BeanDefinition物件,然後呼叫子類的doParse方法解析子節點進行事務屬性的新增。

protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
	
	//將配置的事務管理器新增到屬性
	builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element));
	//獲取tx的子標籤<tx:attributes> 。這裡明確規定了只能有一個子標籤
	List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);
	if (txAttributes.size() > 1) {
		parserContext.getReaderContext().error(
				"Element <attributes> is allowed at most once inside element <advice>", element);
	}
	else if (txAttributes.size() == 1) {
		Element attributeSourceElement = txAttributes.get(0);
		//解析事務方法的屬性。也就是標籤<tx:method name="insert*"/>
		RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);
		
		//將NameMatchTransactionAttributeSource類的例項新增到屬性中。
		//NameMatchTransactionAttributeSource是一個包含了所有的事務屬性的例項
		builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition);
	}
	else {
		builder.addPropertyValue("transactionAttributeSource",
				new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"));
	}
}

private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {
	//獲取所有的子標籤tx:method
	//比如<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>  
	List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);
	
	//事務屬性的Map
	ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =
		new ManagedMap<TypedStringValue, RuleBasedTransactionAttribute>(methods.size());
	transactionAttributeMap.setSource(parserContext.extractSource(attrEle));

	//將method標籤裡面的name為key,其他的事務屬性為value加入Map
	for (Element methodEle : methods) {
		String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);
		TypedStringValue nameHolder = new TypedStringValue(name);
		nameHolder.setSource(parserContext.extractSource(methodEle));

		RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();
		String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);
		String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);
		String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);
		String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);
		if (StringUtils.hasText(propagation)) {
			attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);
		}
		if (StringUtils.hasText(isolation)) {
			attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation);
		}
		if (StringUtils.hasText(timeout)) {
			try {
				attribute.setTimeout(Integer.parseInt(timeout));
			}
			catch (NumberFormatException ex) {
				parserContext.getReaderContext().error("Timeout must be an integer value: [" + timeout + "]", methodEle);
			}
		}
		if (StringUtils.hasText(readOnly)) {
			attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));
		}

		List<RollbackRuleAttribute> rollbackRules = new LinkedList<RollbackRuleAttribute>();
		if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {
			String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);
			addRollbackRuleAttributesTo(rollbackRules,rollbackForValue);
		}
		if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {
			String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);
			addNoRollbackRuleAttributesTo(rollbackRules,noRollbackForValue);
		}
		attribute.setRollbackRules(rollbackRules);

		transactionAttributeMap.put(nameHolder, attribute);
	}
	//建立NameMatchTransactionAttributeSource類,將上面的事務屬性map放入自己的屬性nameMap
	RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class);
	attributeSourceDefinition.setSource(parserContext.extractSource(attrEle));
	attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);
	return attributeSourceDefinition;
}
複製程式碼

然後將TransactionInterceptor的物件返回,此時這個物件的屬性集合中已經包含了事務管理器和所有的事務屬性。

  • 3、註冊TransactionInterceptor

將這個類註冊到bean容器。它的名字就是XML中配置的txAdvice。

this.beanDefinitionNames.add(beanName);
this.beanDefinitionMap.put(beanName, beanDefinition);
複製程式碼

2、advisor的解析

advisor的解析我們在上一章節已經深入分析了,這裡就不過多展開。總之,它最後返回一個DefaultBeanFactoryPointcutAdvisor例項的BeanDefinition物件,重點是它有一個屬性adviceBeanName就是上面已經註冊到容器的txAdvice。

剩下的就是AOP生成代理的流程,實際呼叫Service方法來到AopProxy的invoke方法。還是以JDK動態代理為例,在呼叫Service方法時候,呼叫到JdkDynamicAopProxy.invoke()。或許大家還有印象,它先獲取方法的攔截鏈,也就是通知方法的集合,然後鏈式呼叫它們的invoke。在這裡,通知只有一個,那就是org.springframework.transaction.interceptor.TransactionInterceptor

3、事務控制

TransactionInterceptor.invoke()就是實際處理事務的地方。先來看一下這個方法的全貌。

public class TransactionInterceptor{
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		
		//目標類的Class物件
		//class com.viewscenes.netsupervisor.service.impl.UserServiceImpl
		Class<?> targetClass = AopUtils.getTargetClass(invocation.getThis());
		//事務處理
		return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
			//回撥方法,就是目標類的方法呼叫
			public Object proceedWithInvocation() throws Throwable {
				return invocation.proceed();
			}
		});
	}
}
複製程式碼

可以看出,invokeWithinTransaction方法才是重點。同樣的,我們也先來看一下它的內部實現。

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, 
                  final InvocationCallback invocation)throws Throwable {

	//獲取事務的屬性(傳播行為、隔離級別等)
	final TransactionAttribute txAttr = getTransactionAttributeSource().
									getTransactionAttribute(method, targetClass);
	//獲取事務管理器
	final PlatformTransactionManager tm = determineTransactionManager(txAttr);
	//執行的方法 
	//com.viewscenes.netsupervisor.service.impl.UserServiceImpl.insertUser
	final String joinpointIdentification = methodIdentification(method, targetClass);

	//這個if就是宣告式事務,else就是程式設計式事務,暫不涉及。
	if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
		// Standard transaction demarcation with getTransaction and commit/rollback calls.
		TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification)
		Object retVal = null;
		try {
			//回撥方法,呼叫下一個攔截鏈。
			//但實際上只有這一個通知,所以會呼叫目標物件實際方法。
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			//rollback 回滾事務
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
		}
		finally {
			cleanupTransactionInfo(txInfo);
		}
		//commit 提交事務
		commitTransactionAfterReturning(txInfo);
		return retVal;
	}
}
複製程式碼

看到上面的原始碼,思路就比較清晰了。

  • 獲取事務管理器和事務屬性
  • 執行業務方法
  • 根據try、catch決定回滾還是提交事務

所以這也解答了為什麼業務方法裡不能catch異常,否則事務不會回滾。如果一定要catch異常並且保持事務,那麼在catch之後手動再throw一下異常也是可以的。如下所示:

public void insertUser(User user) {	
	try {
		int i = 1/0;
		System.out.println("----------新增使用者資訊------------");
	} catch (Exception e) {
		System.out.println("UserServiceImpl.insertUser()"+e.getMessage());
		throw new RuntimeException();	
	}
}
複製程式碼

不過在thorw的時候還需要注意,Spring在回滾的時候還有個判斷。也就是說,只要這兩種異常才會回滾。

public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}
複製程式碼

那麼,除了這種手動throw的方式,有沒有其他的呢?當然,還記不記得在TransactionStatus介面中有個方法setRollbackOnly。我們可以設定它,控制事務只可以回滾而不能提交。即便走到commit方法也沒關係,它是有一個判斷的。

if (defStatus.isLocalRollbackOnly()) {
	if (defStatus.isDebug()) {
		logger.debug("Transactional code has requested rollback");
	}
	processRollback(defStatus);
	return;
}
複製程式碼

在業務程式碼中呼叫即可,就像這樣。也可以保證事務會回滾。

public void insertUser(User user) {		
	try {
		int i = 1/0;
		System.out.println("----------新增使用者資訊------------");
	} catch (Exception e) {
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}
}
複製程式碼

看完了整個處理流程,我們的目光再回到createTransactionIfNecessary方法。因為我們想了解事務到底是怎麼被建立的。

  • 從資料來源中獲取一個事務管理器
package org.springframework.jdbc.datasource;
public class DataSourceTransactionManager{
	
	//建立一個資料來源事務管理器,從資料來源獲取一個底層資料庫連線
	//conHolder此時還是為空
	protected Object doGetTransaction() {
		DataSourceTransactionObject txObject = new DataSourceTransactionObject();
		txObject.setSavepointAllowed(isNestedTransactionAllowed());
		ConnectionHolder conHolder =
			(ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
		txObject.setConnectionHolder(conHolder, false);
		return txObject;
	}
}
複製程式碼
  • 判斷事務屬性

判斷事務屬性,是否超時、傳播行為

//事務超時
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
		throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
	}
//說明此方法必須在一個事務中執行。但此時還未開啟事務,所以要丟擲異常。
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
	throw new IllegalTransactionStateException(
			"No existing transaction found for transaction marked with propagation 'mandatory'");
}
複製程式碼
  • 開啟事務 事務是與底層資料庫連線繫結的。
protected void doBegin(Object transaction, TransactionDefinition definition) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	Connection con = null;
	try {
		//從資料來源中獲取一個連線,並放到事務管理器中
		//newCon就是ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@62e9f76b]]
		//這就說明一個事務對應一個資料庫連線
		Connection newCon = this.dataSource.getConnection();
		txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
		txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
		con = txObject.getConnectionHolder().getConnection();

		//設定事務提交方式為手動提交
		if (con.getAutoCommit()) {
			txObject.setMustRestoreAutoCommit(true);
			con.setAutoCommit(false);
		}
		//設定事務的連線狀態
		txObject.getConnectionHolder().setTransactionActive(true);	
		//超時時間
		int timeout = determineTimeout(definition);
		if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
			txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
		}
		// 將會話繫結到執行緒 threadlocal
		if (txObject.isNewConnectionHolder()) {
			TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
		}
	}
}
複製程式碼
  • 封裝事務物件

將事務管理器,事務屬性和執行方法封裝成TransactionInfo物件,並設定事務的狀態,繫結到當前執行緒,最後返回。

protected TransactionInfo prepareTransactionInfo(PlatformTransactionManager tm,
		TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) {
	
	//將事務管理器、事務屬性和方法封裝成TransactionInfo物件
	TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
	if (txAttr != null) {
		//設定事務的狀態
		txInfo.newTransactionStatus(status);
	}
	//將事務繫結到當前執行緒,即ThreadLocal。
	//因為在commit的時候,會判斷當前執行緒是否有事務存在,否則不會提交
	txInfo.bindToThread();
	return txInfo;
}
複製程式碼

六、總結

總的來說,Spring的事務管理分為程式設計式事務和宣告式事務。

  • 基於 TransactionDefinition、PlatformTransactionManager、TransactionStatus 程式設計式事務管理是 Spring 提供的最原始的方式,通常我們不會這麼寫,但是瞭解這種方式對理解 Spring 事務管理的本質有很大作用。

  • 基於 和 名稱空間的宣告式事務管理是目前推薦的方式,其最大特點是與 Spring AOP 結合緊密,可以充分利用切點表示式的強大支援,使得管理事務更加靈活。

我們說基於tx標籤的宣告式事務是與AOP緊密結合的產物,通過對方法的攔截,它實際處理的時候呼叫的其實還是程式設計式事務裡的那幾個介面方法。

相關文章