一篇博客参透spring事务管理原理

spring事务管理原理分析

我们都知道,IOC和AOP是组成spring的两大基石,而AOP面向切面编程过程中生成代理对象的操作,也是在IOC容器初始化中初始化bean的过程中,调用BeanPostProcessor中某一个processor来进行的。
众所周知,AOP的典型应用就是日志的打印以及事务的统一管理,而spring中的事务管理分为编程式事务和声明式事务,但是不管那种实现方式,最终要实现的目标,就是spring框架对事务的统一管理,将事务和业务逻辑进行解耦。
那么,spring要进行统一的事务管理,势必是要对业务逻辑代码进行动态代理,在动态生成的代理对象中,进行事务切面的织入。
JDBC的事务控制最底层一定是通过Connection的统一来实现的,所以,我们只要分析清楚Connection在spring的事务管理器中是如何生成的,之后,在相应的ORM框架中(比如mybatis)是如何获取到的,就可以搞清楚spring的事务管理的原理了。
接下来,我们就从以下两个方面来研究spring的事务管理:(原理说明采用spring+mybatis框架)

  • 一、spring是怎么产生的Connection对象,并且是如何存储的
    首先,要实现spring+mybatis的集成,并且要在spring中进行事务管理的配置,具体的依赖以及spring配置代码如下:
    maven依赖:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.3.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.3</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    

    从maven依赖的配置中,我们可以看到基本上引入了3个方面的内容:

    • 1、spring的依赖
    • 2、mybatis的依赖
    • 3、spring与mybatis连接的依赖

    从上面的依赖可以分析,在spring实现事务管理的过程中,第一项和第二项spring以及mybatis的依必然是为了两大框架本身的功能,从而进一步猜想,spring框架和mybatis框架中Connection的传递,是通过第三项依赖完成的。

    接下来,我们继续看spring的配置:

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.user}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="typeAliasesPackage" value="com.huwc.bean"></property>
        <property name="mapperLocations" value="classpath*:*Mapper.xml"></property>
    </bean>
    <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
        <property name="basePackage" value="com.huwc.mapper"></property>
    </bean>
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    

    从上面的配置文件可以看到,spring主要配置了数据源dataSource,mybatis的sqlSessionFactory以及DataSourceTransactionManager事务管理器和开启基于注解的声明式事务。其中tx:annotation-driver注解主要是为ioc容器中自动注入了n个事务管理需要的组件,例如相关的BeanPostProcessor,此处过程略过不表,我们主要关注Connection的生成和传递过程。
    跟踪代码过程中发现,在事务统一管理的过程中,spring通过非常繁琐的动态代理以及初始化过程后,是通过DataSourceTransactionManager的doBegin方法来开启事务,而开启事务的主要工作,就是获得一个Connection,继而setAutoCommit(false),再将Connection进行封装,从而进行存储。
    show me the code:

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
    	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    	Connection con = null;
    
    	try {
    		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);
    		}
    
    		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()) {
    			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);
    	}
    }
    

    从上面的代码可以看出,spring从数据源获取Connection后,最终是通过TransactionSynchronizationManager的bindResource来进行的资源存储,我们继续看bindResource方法中是做了什么工作:

    public static void bindResource(Object key, Object value) throws IllegalStateException {
    	Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    	Assert.notNull(value, "Value must not be null");
    	Map<Object, Object> map = resources.get();
    	// set ThreadLocal Map if none found
    	if (map == null) {
    		map = new HashMap<>();
    		resources.set(map);
    	}
    	Object oldValue = map.put(actualKey, value);
    	// Transparently suppress a ResourceHolder that was marked as void...
    	if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
    		oldValue = null;
    	}
    	if (oldValue != null) {
    		throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
    				actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
    	}
    	if (logger.isTraceEnabled()) {
    		logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
    				Thread.currentThread().getName() + "]");
    	}
    }
    

    从代码中能够分析出:Connection对象最终是作为value存储在了一个map中,而map的key就是项目中的dataSource,而这个map最终是存储在了TransactionSynchronizationManager的resources属性中,那么这个resources就很重要了,它是怎么能够实现逻辑层面和事务管理层面的Connection资源的共享呢,我们看resource的声明:

    private static final ThreadLocal<Map<Object, Object>> resources =
    		new NamedThreadLocal<>("Transactional resources");
    

    可以看出,resources其实是一个静态的ThreadLocal对象,而我们知道ThreadLocal的功能,就是在同一个线程中实现变量的共享和隔离。而此处的ThreadLocal为什么要存储一个map呢,而且map的key还是一个dataSource,我猜想是为了实现在系统中有多个数据源的时候,可以通过同一个TransactionManager进行事务的管理 。

  • 二、mybatis框架在持久化过程中,是如何获取Connection的
    我们跟踪mybatis的持久化执行过程,得出了图中展示的调用栈:
    在这里插入图片描述
    从图中可以看出,在mybatis框架需要Connection对象的时候,是通过上述第三项依赖中的SpringManageredTransaction调用到了第一项依赖中spring框架中DataSrouceUtils的doGetConnection方法来获取Connection对象的,那么我们分析,此方法必然是会调用上面说到的静态ThreadLocal对象获取,我们来看代码:

    @Nullable
    public static Object getResource(Object key) {
    	Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    	Object value = doGetResource(actualKey);
    	if (value != null && logger.isTraceEnabled()) {
    		logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
    				Thread.currentThread().getName() + "]");
    	}
    	return value;
    }
    
    /**
     * Actually check the value of the resource that is bound for the given key.
     */
    @Nullable
    private static Object doGetResource(Object actualKey) {
    	Map<Object, Object> map = resources.get();
    	if (map == null) {
    		return null;
    	}
    	Object value = map.get(actualKey);
    	// Transparently remove ResourceHolder that was marked as void...
    	if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
    		map.remove(actualKey);
    		// Remove entire ThreadLocal if empty...
    		if (map.isEmpty()) {
    			resources.remove();
    		}
    		value = null;
    	}
    	return value;
    }
    

    果然,没有令我们失望,代码的展示符合我们的预想,通过数据源dataSource和当前线程,就可以获取到spring框架生成的Connection对象,最终实现spring框架对事务的统一管理。
    通过源码的阅读过程,可以更加深刻的理解spring以及第三方框架的集成的底层原理。当然,本文只是作者本人的理解和分析,肯定存在疏漏和错误以及不到位的地方,欢迎小伙伴一起讨论学习,共同进步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值