springboot究竟是怎么实现事务控制的

358 阅读3分钟

1、主要是讨论在与Mybatis结合的时候事务的控制是怎么实现的

讲几个大前提,spring的事务管理是基于aop实现的,那么可以理解为,在开启事务后,spring提供一些必要的对原方法的增强,即事务相关的处理,增强原方法。

1、在springboot中使用事务管理时,首先会使用这个注解@EnableTransactionManagement

那么这个注解起了什么作用?在实现事务控制的过程中发挥了什么作用?

最关键的就是这个Import,Import的作用,可以暂时理解为引入,我们可以认为,引入的这个类会被做一些处理,以达到一些目的。那么继续看看引入的这个类包含了什么内容。此前,可以从引入的这个类的名字看出这是个事务管理配置选择器。

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

	/**
	 * 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);
	}

}

简单来说就是一个AbstractTransactionManagementConfiguration选择器,selectImports方法,根据参数选择不同的事务管理配置。实际中是PROXY模式。

这里讲一些其他的事情,这个类TransactionManagementConfigurationSelector是通过Import注解引入的,在某个时机会处理通过Import注解导入的类。这个类的父类实现了这个接口ImportSelector,在处理TransactionManagementConfigurationSelector时,发现他实现了ImportSelector接口,会调用selectImports接口,返回要导入的内容。 我们看这里会返回什么?

进入PROXY分支,返回了

  • AutoProxyRegistrar.class.getName()
  • ProxyTransactionManagementConfiguration.class.getName()

下面看看这两个东西是什么?

  • AutoProxyRegistrar 实现了ImportBeanDefinitionRegistrar,会注入BeanDefinition(代理创建器)
  • ProxyTransactionManagementConfiguration 就是个普通的类,也会把它注入容器中

重点看下ProxyTransactionManagementConfiguration这个类究竟都有什么?

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    
    // 顾名思义,一个advisor就是增强器
	@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;
	}

    // 用来获取@transactinal注解信息的工具
	@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在什么时候起的作用,以及起的什么作用。最终就是aop把目标方法代理了。这个advisor就是事务处理的部分了,更多的细节就是aop是怎么做的里面了。

spring事务的传播

spring事务的传播指的是

a(){
    b();
}

即在方法中又调用方法的 这两个方法之间的传播。

3、spring的事务管理究竟是怎么运行的?

在只使用jdbc时事务是这样使用

  • 1、获取一个connection
  • 2、设置这个connection手动提交,即自动提交为false
  • 3、在执行完操作后,commit或rollback

那在spring中管理事务也一定离不开这三步,但在spring中因为操作因为聚合多个系统产生了一定的复杂性,但总体还是这个过程。

3.1、上述被称为核心流程

那么先看看在spring中核心流程是怎么处理的。

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

		try {
		    // 这里很简单 就是获取一个connection,但是可以看到这里有一个connecHolder
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				Connection newCon = obtainDataSource().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);
			设置是否只读
			txObject.setReadOnly(definition.isReadOnly());
            
                        // 设为手动提交事务
			// 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);
			}
            // 如果是只读模式就使用SET TRANSACTION READ ONLY这个sql设置一下
			prepareTransactionalConnection(con, definition);
			txObject.getConnectionHolder().setTransactionActive(true);

			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}
                // 将connection绑定到当前线程,使用ThreadLocal
			// Bind the connection holder to the thread.
			if (txObject.isNewConnectionHolder()) {
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			}
		}

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

经过这个方法,就完成这个核心流程