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以及第三方框架的集成的底层原理。当然,本文只是作者本人的理解和分析,肯定存在疏漏和错误以及不到位的地方,欢迎小伙伴一起讨论学习,共同进步。