spring事务管理源码分析(二)事务处理流程分析

news/2024/7/7 20:27:43

如果我们使用默认的配置,(即不修改@EnableTransactionManagement注解的默认值),那么spring内部将利用动态代理的方式(即JdkDynamicAopProxy对象)嵌入了事务管理的逻辑。如果对动态代理不熟悉的看官可以移步spring-AOP(一)实现原理参考阅读。

这里先大体了解下事务管理流程的操作顺序

其内部机制整体的逻辑就是:

  1. 从数据库中获取连接,并绑定到线程中
  2. 执行被代理的业务方法
  3. 如果业务方法执行正常,提交事务;并且在DataSourceTransactionManager的实现中,可以注册回调让其在事务提交后进行执行(应用场景:1. 业务下单后,注册一个推送MQ消息的回调,此时如果正常提交下单的数据,则同步推送一个MQ消息;2. 清理缓存)
  4. 如果执行异常,则执行回滚操作

源码分析

调用事务增强的代理类,其背后发生什么?

JdkDynamicAopProxy作为动态代理的InvocationHandler的实现类,在调用事务增强的代理对象时,将通过invoke方法拦截调用,我们来看一下其内部实现。借助ReflectiveMethodInvocation实现拦截链的执行,默认拦截链中存在拦截器TransactionInterceptor,最终在TransactionInterceptor拦截逻辑内部进行连接获取、开启事务、执行业务方法、提交事务、回滚事务操作

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

    @Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		MethodInvocation invocation;
		Object oldProxy = null;
		boolean setProxyContext = false;

		TargetSource targetSource = this.advised.targetSource;
		Class<?> targetClass = null;
		Object target = null;

		try {
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}

			Object retVal;

			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			// May be null. Get as late as possible to minimize the time we "own" the target,
			// in case it comes from a pool.
			target = targetSource.getTarget();
			if (target != null) {
				targetClass = target.getClass();
			}

			// Get the interception chain for this method.
            // 获取方法拦截器MethodInterceptor列表。默认为TransactionInterceptor
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			// 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.
                // 通过ReflectiveMethodInvocation调用,实现MethodInterceptor拦截链的执行
				retVal = invocation.proceed();
			}

			// Massage return value if necessary.
			Class<?> returnType = method.getReturnType();
			if (retVal != null && retVal == target &&
					returnType != Object.class && returnType.isInstance(proxy) &&
					!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
				// Special case: it returned "this" and the return type of the method
				// is type-compatible. Note that we can't help if the target sets
				// a reference to itself in another returned object.
				retVal = proxy;
			}
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			return retVal;
		}
		finally {
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

}

// ReflectiveMethodInvocation
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {

    @Override
	public Object proceed() throws Throwable {
		//	We start with an index of -1 and increment early.
        // 当执行完拦截链上所有的MethodInterceptor后,调用业务方法
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
            // 执行MethodInterceptor拦截器,即TransactionInterceptor
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

}
复制代码

业务方法以何种方式以及如何被调用的?

TransactionInterceptor作为MethodInterceptor,拦截了业务方法的执行,其invoke方法如下: 在invoke方法中,嵌入事务管理的逻辑代码,并调用业务方法

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
    @Override
    public Object invoke(final 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<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

        // Adapt to TransactionAspectSupport's invokeWithinTransaction...
        // 在父类TransactionAspectSupport中定义,该方法内部进行连接获取、开启事务、执行业务方法、提交事务、回滚事务操作
        return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
            @Override
            public Object proceedWithInvocation() throws Throwable {
                // 调用ReflectiveMethodInvocation#proceed,进行下一个拦截器的执行或者调用业务方法
                return invocation.proceed();
            }
        });
    }
}

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

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

		// If the transaction attribute is null, the method is non-transactional.
        // 获取@Transactional定义的事务属性
		final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
        // 获取事务管理器,mybatis和jdbctemplate的话默认都是DataSourceTransactionManager
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		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 {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
                // 执行下一个拦截器或者调用业务方法
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// target invocation exception
                // 回滚事务
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
                // 清理资源
				cleanupTransactionInfo(txInfo);
			}
            // 提交事务
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
			final ThrowableHolder throwableHolder = new ThrowableHolder();

			// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
			try {
				Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
						new TransactionCallback<Object>() {
							@Override
							public Object doInTransaction(TransactionStatus status) {
								TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
								try {
									return invocation.proceedWithInvocation();
								}
								catch (Throwable ex) {
									if (txAttr.rollbackOn(ex)) {
										// A RuntimeException: will lead to a rollback.
										if (ex instanceof RuntimeException) {
											throw (RuntimeException) ex;
										}
										else {
											throw new ThrowableHolderException(ex);
										}
									}
									else {
										// A normal return value: will lead to a commit.
										throwableHolder.throwable = ex;
										return null;
									}
								}
								finally {
									cleanupTransactionInfo(txInfo);
								}
							}
						});

				// Check result state: It might indicate a Throwable to rethrow.
				if (throwableHolder.throwable != null) {
					throw throwableHolder.throwable;
				}
				return result;
			}
			catch (ThrowableHolderException ex) {
				throw ex.getCause();
			}
			catch (TransactionSystemException ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
					ex2.initApplicationException(throwableHolder.throwable);
				}
				throw ex2;
			}
			catch (Throwable ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
				}
				throw ex2;
			}
		}
	}

}
复制代码

如何获取数据库连接?

获取连接的逻辑封装在DataSourceTransactionManager#doBegin方法中

protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;

    try {
        if (!txObject.hasConnectionHolder() ||
                txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            // 获取连接
            Connection newCon = this.dataSource.getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();

        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);

        // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
        // so we don't want to do it unnecessarily (for example if we've explicitly
        // configured the connection pool to set it already).
        // 关闭获取到连接的自动提交属性
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
            con.setAutoCommit(false);
        }

        prepareTransactionalConnection(con, definition);
        txObject.getConnectionHolder().setTransactionActive(true);

        int timeout = determineTimeout(definition);
        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }

        // Bind the connection holder to the thread.
        if (txObject.isNewConnectionHolder()) {
            // 绑定连接,其内部使用ThreadLocal实现。达到隔离每个线程的数据库连接的目的
            TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
        }
    }

    catch (Throwable ex) {
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, this.dataSource);
            txObject.setConnectionHolder(null, false);
        }
        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
    }
}
复制代码

http://www.niftyadmin.cn/n/1898053.html

相关文章

EF对DropDownList绑定数据

先拉一个控件&#xff1a;DropDownList <asp:DropDownList ID"ddl_Class" runat"server" CssClass"form-control"></asp:DropDownList> 后台代码展示&#xff1a; using (var db new DemoEntities()){//1.查询出当前修改的学员信…

Linux入门学习日志(三)

目录 零、相关小提示 一、帮助命令 1.man命令 2.help命令 二、文件目录类命令 1.pwd&#xff1a; 2.ls常用用法&#xff1a; 3.cd命令 用法1&#xff1a;普通绝对路径目录切换 用法2&#xff1a;切换到家目录的简易方法 用法3&#xff1a;相对路径切换目录 4.mkdir创…

解决zabbix的中文乱码

CentOS7.1 x64上下载了zabbix官方的rpm包&#xff0c;导入后使用yum安装了zabbix 3.2.6&#xff0c;但是启动zabbix server的时候报了个段错误的错&#xff0c;谷歌了一会儿&#xff0c;发现段错误不止一次的出现在了历史的bug中。解决方法除了官方修复bug外&#xff0c;还有就…

使用ADO.NET来对DropDownList进行数据绑定

首先先拉一个DropDownList控件 <asp:DropDownList ID"ddl_Class" runat"server" CssClass"form-control"></asp:DropDownList> 后台代码编写&#xff1a; editId Convert.ToInt32(Request.QueryString["Id"]); //2.1定…

CF600E:Lomsat gelral(线段树合并)

Description 一棵树有n个结点&#xff0c;每个结点都是一种颜色&#xff0c;每个颜色有一个编号&#xff0c;求树中每个子树的最多的颜色编号的和。Input 第一行一个$n$。第二行$n$个数字是$c[i]$。后面$n-1$行给出树边。Output 一行答案。Sample Input1 41 2 3 41 22 32 4Samp…

Java 8学习资料汇总

Java 8发布已经有一段时间&#xff0c;它被认为是Java 5发布以来最大的一次版本升级。Java 8 为Java语言、编译器、类库、开发工具以及JVM&#xff08;Java虚拟机&#xff09;带来了大量新特性。Lambda表达式、默认方法、并行API等都受到了开发者的追捧&#xff0c;社区上关于J…

css 3d旋转图片(css旋转木马)(详细教程)

成品展示接下来开始制作旋转老婆图片一、准备html骨架和基本的css先准备好html骨架(后面为了挨个测试可以先把后面5个div注释掉)<div class"bigbox"><div><img src"img/w1.jpg" alt""></div><div><img src&quo…

虚拟机Ubuntu设置ssh连接

检查windows主机和虚拟机是否能互相ping通。 检查是否安装ssh服务&#xff0c;输入以下命令&#xff1a; ssh localhost 复制代码 若出现以下信息&#xff0c;表示还没安装&#xff1a; ssh: connect to hostlocalhost port 22: Connection refused 复制代码 安装ssh服务…