天天看點

Spring架構淺析 -- 資料庫事務處理什麼是資料庫事務為什麼Spring需要對資料庫事務處理進行支援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中,自然也不會被事務所支援。