1. 程式人生 > >Spring框架淺析 -- 資料庫事務處理

Spring框架淺析 -- 資料庫事務處理

概述

Spring框架淺析 -- 概述中,我們介紹過,Spring對於資料庫操作及事務處理的支援,是Spring功能中較為重要的一環。那麼資料庫事務是什麼?為什麼要Spring需要對資料庫操作及事務處理進行支援?Spring都提供了哪些方式對資料庫事務處理進行支援?這些方式中又有哪種比較適合當前工程開發的實踐?在本文中,我們將對這些問題一一展開闡述。

什麼是資料庫事務

相信這個問題,具備J2EE開發經驗的同學都不陌生。詳情可見:https://zh.wikipedia.org/zh-hans/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BA%8B%E5%8A%A1

為什麼Spring需要對資料庫事務處理進行支援

對於J2EE領域的開發,通常都需要引入關係型資料庫用來儲存關係型資料。作為框架,Spring當然要提供通用的資料庫操作、資料庫事務處理的功能,從而減少業務方重複造輪子,重複封裝這些功能,向上遮蔽底層操作不同資料庫、管理資料庫事務的細節。試想,如果Spring不提供這些功能,每次開發新業務,都需要引入一大堆重複的獲取資料庫連線,開啟資料庫事務,關閉資料庫事務,回滾資料庫事務的程式碼,將是一件十分不優雅的事情。

Spring提供了哪些方式支援資料庫事務

在本文中,我們暫時不考慮分散式事務。分散式事務相關問題,我們會在後續的文章中加以敘述。

對於非分散式事務,Spring中最為常用的事務管理器為org.springframework.jdbc.datasource.DataSourceTransactionManager。Spring基於DataSourceTransactionManager提供了兩種方式來支援資料庫事務處理,程式設計式和宣告式。

程式設計式即為,在業務程式碼中,通過程式設計的方式,使用DataSourceTransactionManager的setDataSource(設定資料來源,關係型資料庫例項),doBegin(開始事務),doCommit(提交事務),doRollback(回滾事務),完成一次資料庫操作。這種方式的缺點是明顯的,就是對於通用的資料庫操作(操作行為大致相同,只是資料不同,可抽象成為統一的行為),程式碼侵入太強,不夠優雅。

宣告式即為,在操作資料庫的bean的函式上,使用@Transactional標註,宣告該方法內部將進行資料庫事務處理。藉助於AOP,容器在載入該bean的時候,對其進行增強,在標註了@Transactional的方法執行前和執行後動態織入資料庫事務處理的相關程式碼,也就是藉助Spring容器將程式設計式事務處理的程式碼動態織入進去,無需在業務程式碼中體現,對於不同的配置項(比如回滾條件等)配置在@Transactional上。

在日常實踐中,我們通常使用宣告式事務處理的方式,因為它將業務程式碼與通用資料庫處理程式碼分離開來,夠優雅,夠靈活。

Spring宣告式事務處理的原理

在介紹Spring宣告式事務處理的原理之前,我們先來看一下對事務處理器,如何進行配置。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="xxxDataSource">
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>

結合配置檔案以及Spring框架淺析 -- IoC容器與Bean的生命週期中關於獲取BeanFactory、自定義名稱空間解析以及Bean的生命週期的相關介紹,可以梳理出Spring宣告式事務處理的執行過程,從而瞭解到如何對使用了@Transactional註解的方法進行增強,動態織入事務處理相關的邏輯。

步驟一

Spring容器啟動的時候,按照配置檔案中的配置,向BeanFactory中註冊事務處理器DataSourceTransactionManager,將資料來源xxxDataSource設定到其dataSource屬性。

步驟二

當解析到<tx:annotation-driven transaction-manager="transactionManager"/>時,由於這是一個自定義namespace,所以使用tx名稱空間的namespaceHandler-TxNamespaceHandler進行解析(namespace到namespaceHandler的對映關係配置在spring-tx jar包下的META-INF目錄下)。

步驟三

在TxNamespaceHanlder中,根據xml配置檔案中配置的transaction-manager屬性,獲取到其對應的BeanDefinitionParser-AnnotationDrivenBeanDefinitionParser,然後呼叫其parse方法,在其呼叫鏈中,向容器中以"org.springframework.aop.config.internalAutoProxyCreator"為beanName註冊InfrastructureAdvisorAutoProxyCreator,這是一個實現了InstantiationAwareBeanPostProcessor介面的BeanPostProcessor,所以其會在Bean的例項化、初始化過程中對Bean進行加工修飾。此外,還註冊了AnnotationTransactionAttributeSource、TransactionInterceptor、BeanFactoryTransactionAttributeSourceAdvisor,這三個元件也會在下邊的步驟中起作用。

步驟四

在Bean初始化之後,獲取註冊的BeanPostProcessor集合(這裡邊包含了InfrastructureAdvisorAutoProxyCreator),呼叫其postProcessAfterInitialization方法,獲取Bean的proxy,在代理中,針對標註了@Transactional的方法,進行增強,將事務處理相關邏輯動態織入。

我們一步一步地分析一下這個呼叫鏈。

InfrastructureAdvisorAutoProxyCreator的postProcessAfterInitialization方法,實質上呼叫的是其父類AbstractAutoProxyCreator。

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (!this.earlyProxyReferences.contains(cacheKey)) {
                                // 主入口在這裡
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

從主入口進入

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
        // 獲取Interceptors
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			// 建立proxy
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

從以上程式碼看出,主要步驟有兩個,一個是獲取Interceptors,一個是基於Interceptors對當前處理的bean生成proxy。

說到proxy,其實瞭解一下代理模式,會對為什麼要生成proxy更加明晰。代理模式相關介紹參見:https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F

簡單說就是,為被代理物件建立代理,代理對外部遮蔽了被代理物件的內部邏輯。當外部按照協議試圖呼叫被代理物件的方法時,實際上是落在了代理上,由代理加入增強邏輯,然後再呼叫被代理物件,將結果封裝,最後返回給外部呼叫方。由於呼叫過程中經過了代理,所以代理當然可以加入相應的增強邏輯,比如計數、記錄呼叫引數和返回結果、事務處理。

上邊wikipedia中的附圖能夠清晰地說明這些點。

我們依然回到程式碼中,獲取Interceptors的過程如下:

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		// 獲取候選Advisors,由於上文提到的BeanFactoryTransactionAttributeSourceAdvisor就是Advisor,所以會被加入
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		// 獲取符合條件的Advisors
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}

我們先進入findCandidateAdvisors方法,最為重要的程式碼如下,本質上是獲取到型別為Advisor的類。我們回到步驟三,可以看到BeanFactoryTransactionAttributeSourceAdvisor其實就是一個Advisor,因此也會被加入到Advisor候選集中。

advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);

再回到findAdvisorsThatCanApply方法

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
		if (candidateAdvisors.isEmpty()) {
			return candidateAdvisors;
		}
		List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
		for (Advisor candidate : candidateAdvisors) {
			if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
				eligibleAdvisors.add(candidate);
			}
		}
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();
		for (Advisor candidate : candidateAdvisors) {
			if (candidate instanceof IntroductionAdvisor) {
				// already processed
				continue;
			}
			// 對於BeanFactoryTransactionAttributeSourceAdvisor,它是PointcutAdvisor,所以走這個分支
			if (canApply(candidate, clazz, hasIntroductions)) {
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}

再進入到canApply

	public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
		if (advisor instanceof IntroductionAdvisor) {
			return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
		}
		else if (advisor instanceof PointcutAdvisor) {
			PointcutAdvisor pca = (PointcutAdvisor) advisor;
			return canApply(pca.getPointcut(), targetClass, hasIntroductions);
		}
		else {
			// It doesn't have a pointcut so we assume it applies.
			return true;
		}
	}

注意其中的pca.getPointcut,這個返回值如下所示:

	private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
		@Override
		protected TransactionAttributeSource getTransactionAttributeSource() {
			return transactionAttributeSource;
		}
	};

接下來看canApply方法

	public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
		Assert.notNull(pc, "Pointcut must not be null");
		if (!pc.getClassFilter().matches(targetClass)) {
			return false;
		}

		// 呼叫StaticMethodMatcherPointcut(TransactionAttributeSourcePointcut的父類)的方法,返回值為this
		MethodMatcher methodMatcher = pc.getMethodMatcher();
		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}

		Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
		classes.add(targetClass);
		for (Class<?> clazz : classes) {
			Method[] methods = clazz.getMethods();
			// 遍歷bean中的方法,關注if判斷的後一個判斷表示式
			for (Method method : methods) {
				if ((introductionAwareMethodMatcher != null &&
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
						methodMatcher.matches(method, targetClass)) {
					return true;
				}
			}
		}

		return false;
	}

關注其中的methodMatcher.matches方法

	public boolean matches(Method method, Class<?> targetClass) {
		if (TransactionalProxy.class.isAssignableFrom(targetClass)) {
			return false;
		}
		TransactionAttributeSource tas = getTransactionAttributeSource();
		return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
	}

tas實際上就是步驟三中設定的AnnotationTransactionAttributeSource,呼叫其getTransactionAttribute方法其實是呼叫其父類的同名方法,其中核心在於

	protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
		// Don't allow no-public methods as required.
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
			return null;
		}

		// Ignore CGLIB subclasses - introspect the actual user class.
		Class<?> userClass = ClassUtils.getUserClass(targetClass);
		// The method may be on an interface, but we need attributes from the target class.
		// If the target class is null, the method will be unchanged.
		Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
		// If we are dealing with method with generic parameters, find the original method.
		specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

		// First try is the method in the target class.
		// 類中的方法標註了@Transactional為第一優先順序
		TransactionAttribute txAtt = findTransactionAttribute(specificMethod);
		if (txAtt != null) {
			return txAtt;
		}

		// 類上標註了@Transactional為第二優先順序
		// Second try is the transaction attribute on the target class.
		txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());
		if (txAtt != null) {
			return txAtt;
		}

		if (specificMethod != method) {
			// Fallback is to look at the original method.
			txAtt = findTransactionAttribute(method);
			if (txAtt != null) {
				return txAtt;
			}
			// Last fallback is the class of the original method.
			return findTransactionAttribute(method.getDeclaringClass());
		}
		return null;
	}

我們僅以方法上標註@Transactional為例,分析一下是如何對標註進行解析的

	protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {
		if (ae.getAnnotations().length > 0) {
			for (TransactionAnnotationParser annotationParser : this.annotationParsers) {
				// 實現TransactionAnnotationParser介面的共有三個
				// 1. Ejb3TransactionAnnotationParser
				// 2. JtaTransactionAnnotationParser
				// 3. SpringTransactionAnnotationParser
				// 我們以3為例進行分析
				TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);
				if (attr != null) {
					return attr;
				}
			}
		}
		return null;
	}

以SpringTransactionAnnotationParser為例,終於看到了我們熟悉的標註@Transactional

	public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
		AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ae, Transactional.class);
		if (attributes != null) {
			return parseTransactionAnnotation(attributes);
		}
		else {
			return null;
		}
	}

SpringTransactionAnnotationParser識別@Transactional標註,並解析其中的屬性,屬性列表詳見:https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html

至此,我們找到了@Transactional標註的方法,且識別了標註中的屬性,下一步就是建立proxy,注入事務處理邏輯了

步驟五

上一步中獲取到interceptor之後,這一步藉助於AbstractAutoProxyCreator的createProxy方法開始建立代理。

我們注意到createProxy中的最後一句:

return proxyFactory.getProxy(getProxyClassLoader());

再進一步:

	public Object getProxy(ClassLoader classLoader) {
		return createAopProxy().getProxy(classLoader);
	}

先看createAopProxy方法

	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			// 目標類如果是介面實現類,使用JDK建立動態代理
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			// 否則,使用cglib建立代理
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

JDK動態代理和CgLib代理的區別與聯絡詳見:http://www.cnblogs.com/binyue/p/4519652.html

我們以JDK動態代理為例,JDK動態代理的原理可參考這篇文章:http://www.importnew.com/23168.html。

簡答說,proxy由JDK在執行時動態生成,然後裝載到JVM中,就像手寫的class一樣對外提供服務,只不過其每一個代理方法,都需要呼叫invocationHander.invoke,在該方法就可以加入需要增強的邏輯。

看一下JdkDynamicAopProxy的getProxy方法

	public Object getProxy(ClassLoader classLoader) {
		if (logger.isDebugEnabled()) {
			logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
		}
		Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
		findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
		return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
	}

可以看到最後一句,直接使用JDK中提供的方式,建立代理,並返回,至此完成了代理的建立。

不難看出,JdkDynamicAopProxy實現了重要的InvocationHandler方法,其invoke方法中比較重要的部分如下

// Check whether we have any advice. If we don't, we can fallback on direct
			// reflective invocation of the target, and avoid creating a MethodInvocation.
			if (chain.isEmpty()) {
				// We can skip creating a MethodInvocation: just invoke the target directly
				// Note that the final invoker must be an InvokerInterceptor so we know it does
				// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// We need to create a method invocation...
				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// Proceed to the joinpoint through the interceptor chain.
				retVal = invocation.proceed();
			}

簡答說就是同一個類的方法可能被多個interceptor按鏈式進行代理,假設interceptor鏈為空,那麼就呼叫其方法;

否則,就依次呼叫其interceptor鏈的代理方法。

在這裡,我們僅關注事務處理相關的interceptor,即TransactionInterceptor(步驟三中注入的),其invoke方法內部呼叫了invokeWithinTransaction。在該方法內部,就可以看到其基於TransactionManager進行事務開啟、遇異常回滾、提交等操作。

這樣,當外部訪問該bean時,實際上訪問的是其已注入Spring事務處理功能的proxy,自然會得到事務處理的支援,如開啟事務,提交事務,事務回滾等。

宣告式事務處理使用須知

關於這部分,其實網上有著大量的介紹,如https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html。但其實你只需要關注一點即可:

宣告式事務處理是基於動態proxy實現的。

所以,如果你標註了@Transactional的方法,無法體現在proxy中(無論是自呼叫還是非public方法),都不會體現在其proxy中,自然也不會被事務所支援。