Spring 与 MyBatis 事务管理源码解析
Spring 与 MyBatis
简要概述
- 在配置文件中对service层的AOP实现,使得SpringMVC等servlet调用容器中的Service对象时,会用TransactionInterceptor对其进行动态处理,为其调用DataSourceTransactionManager为其创建transaction并将connection绑定到ThreadLocal中(service对象一经从容器中获取就执行,使得commit等交易transaction管理,sqlsession保存在线程中,可重复使用,否则sqlsession是由每个单独的MapperFactoryBean调用的,每个执行完便提交,再次调用会获取不同的connection,一级缓存失效;就算你不自动提交(没开启事务一般是自动commit),不开启事务也不能保证同一个sqlsession调用的是同一个connection,可能出现更大的问题如脏读等,若是绑定同一个则又是事务的原理了)
- 对service中的方法进行调用时,就需要调用到dao层的方法与mysql进行交互,而dao层的经过MapperScannerConfigurer的自动扫描以及将方法封装成各个MapperFactoryBean
- MapperFactoryBean创建时若没有指定特定的注入对象,在初始化时Spring会自动注入相应的sqlsessionTemplate等sqlsession对象(实现了sqlsession接口,但都不是实际负责sql语句执行的)
- 若是实现了事务管理,则会在TransactionSynchronizationManager寻找真正执行sql语句的且在同一事务中的DefaultSqlsession这类sqlsession真正的实现类,通过SpringManageTransaction调用datasourceUtils,从TransactionSynchronizationManager获取唯一的connection去执行相应的sql语句。
- 若是声明式事务,则在service方法执行完后自动commit;若编程式则从status获取transaction进行commit
<context:component-scan base-package="cn.itcast">//扫描的包
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">//数据源
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
</bean>
//创建特定的SqlSessionFactory
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="comboPooledDataSource"/>
</bean>
// 查 找 类 路 径 下 的 映 射 器 并自 动 将 它 们 创 建 成 MapperFactoryBean
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.itcast.dao"/>
</bean>
//事务管理
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="comboPooledDataSource"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="find*" read-only="true"/>
<tx:method name="*" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.itcast.service.impl.*ServiceImpl.*(..))"/>
</aop:config>
MapperScannerConfigurer与MapperFactoryBean
- MapperScannerConfigurer查 找 类 路 径 下 的 映 射 器 并自 动 将 它 们 创 建 成 MapperFactoryBean
basePackage 属性是让你为映射器接口文件设置基本的包路径。 你可以使用分号或逗号 作为分隔符设置多于一个的包路径。每个映射器将会在指定的包路径中递归地被搜索到。
如果不是有特殊需求,不需要 去 指 定 SqlSessionFactory 或 SqlSessionTemplate , 因 为 MapperScannerConfigurer 将会创建 MapperFactoryBean,之后自动装配。但是,如果你使 用了一个 以上的 DataSource ,那 么自动 装配可 能会失效 。这种 情况下 ,你可 以使用 sqlSessionFactoryBeanName 或 sqlSessionTemplateBeanName 属性来设置正确的 bean 名 称来使用。
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
//接口包
private String basePackage;
private boolean addToConfig = true;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private String sqlSessionFactoryBeanName;
private String sqlSessionTemplateBeanName;
private Class<? extends Annotation> annotationClass;
private Class<?> markerInterface;
private ApplicationContext applicationContext;
private String beanName;
private boolean processPropertyPlaceHolders;
private BeanNameGenerator nameGenerator;
···
get/set方法用以注入
···
}
private void processPropertyPlaceHolders() {
Map<String, PropertyResourceConfigurer> prcs = this.applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
if (!prcs.isEmpty() && this.applicationContext instanceof GenericApplicationContext) {
BeanDefinition mapperScannerBean = ((GenericApplicationContext)this.applicationContext).getBeanFactory().getBeanDefinition(this.beanName);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(this.beanName, mapperScannerBean);
Iterator var4 = prcs.values().iterator();
while(var4.hasNext()) {
PropertyResourceConfigurer prc = (PropertyResourceConfigurer)var4.next();
prc.postProcessBeanFactory(factory);
}
PropertyValues values = mapperScannerBean.getPropertyValues();
this.basePackage = this.updatePropertyValue("basePackage", values);
this.sqlSessionFactoryBeanName = this.updatePropertyValue("sqlSessionFactoryBeanName", values);
this.sqlSessionTemplateBeanName = this.updatePropertyValue("sqlSessionTemplateBeanName", values);
}
}
- MapperFactoryBean
数据映射器接口可以按照如下做法加入到 Spring 中:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
MapperFactoryBean 创建的代理类实现了 UserMapper 接口,并且注入到应用程序中。 因为代理创建在运行时环境中(Runtime,译者注) ,那么指定的映射器必须是一个接口,而 不是一个具体的实现类。
如果 UserMapper 有一个对应的 MyBatis 的 XML 映射器文件, 如果 XML 文件在类路径的 位置和映射器类相同时, 它会被 MapperFactoryBean 自动解析。 没有必要在 MyBatis 配置文 件 中 去 指 定 映 射 器 , 除 非 映 射 器 的 XML 文 件 在 不 同 的 类 路 径 下 。
当 MapperFactoryBean 需要 SqlSessionFactory 或 SqlSessionTemplate 时。 这些可以通过各自的 SqlSessionFactory 或 SqlSessionTemplate 属性来设置, 或者可以由 Spring 来自动装配。如果两个属性都设置了,那么 SqlSessionFactory 就会被忽略,因为 SqlSessionTemplate 是需要有一个 session 工厂的设置; 那个工厂会由 MapperFactoryBean. 来使用。
-
由于使用spring管理bean,当我们在代码中需要使用这个bean的时候,会首先去容器中找,第一次需要调用MapperFactoryBean的getObject方法获取一个bean,并保存到容器中。
MapperFactoryBean的getObject方法如下:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { public T getObject() throws Exception { return this.getSqlSession().getMapper(this.mapperInterface); } }
- 又到SqlSessionDaoSupport中去获取
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; private boolean externalSqlSession; public SqlSession getSqlSession() { return this.sqlSession; } // 在MapperScannerConfigurer自动扫描生成/自己创建MapperFactoryBean时,由传入的工厂类型/Spring自动配置的工厂类型 时,就创建了SqlSessionTemplate(实现了sqlsession接口),因此调用方法时会通过SqlSessionTemplate的proxysqlsession(动态代理生成的代理类)去TransactionSynchronizationManager寻找真正的sqlsession或创建 public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } } //spring整合mybatis后,非事务环境下,每个执行完便提交,每次操作数据库都使用新的sqlSession对象,获取不同的connection。因此mybatis的一级缓存无法使用,若有则取TransactionSynchronizationManager中的sqlsession则一级缓存生效(一级缓存针对同一个sqlsession有效)
TransactionDefinition与TransactionAttributes
-
获取事务属性对象(TransactionAttributes若无设置attributes则默认为DefaultTransactionDefinition),放置在容器中,transactionManager调用getTransaction时作为参数传入
事务属性对象持有事务的相关配置,比如事务的隔离级别,传播行为,是否只读等。我们开启spring事务管理时,通常都会在配置文件里加入这样一段配置。
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
-
TransactionDefinition主要定义了有哪些事务属性可以指定:
-
事务的隔离级别(Isolation)
-
事务的传播行为(Propagation Behavior)
默认为PROPAGATION_REQUIRED ,若存在事务则加入当前事务,若没有则自己新建一个事务。
Propagation Behavior注意PROPAGATION_REQUIRES_NEW和PROPAGATION_NESTED的区别,前者将当前事务挂起,创建新的事务执行;后者,则是在当前事务种的一个嵌套事务中执行
-
事务的超时时间(Timeout)
-
是否为只读事务(ReadOnly)
-
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
-
ISOLATION_DEFAULT 数据库的默认级别:mysql为Repeatable Read(可重读),其他的一般为Read Committed(读取提交内容)
-
Read Uncommitted(读取未提交内容) ISOLATION_READ_UNCOMITTED
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
-
Read Committed(读取提交内容)ISOLATION_READ_COMITTED
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的**不可重复读(Nonrepeatable Read),如果一个用户在一个事务中多次读取一条数据,而另外一个用户则同时更新啦这条数据,造成第一个用户多次读取数据不一致。****
- Repeatable Read(可重读)ISOLATION_REPEATABLE_READ
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指第一个事务读取一个结果集后,第二个事务,对这个结果集经行增删操作(第一个事务暂时没用到,没添加行锁),然而第一个事务中再次对这个结果集进行查询时,数据发现丢失或新增,出现了“幻影”。通过加表级锁解决,如间隙锁InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable(可串行化)ISOLATION_SERIALIZABLE
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
-
-
TransactionAttribute在TransactionDefinition的基础上添加了rollbackon方法,可以通过声明的方式指定业务方法在抛出的哪些的异常的情况下可以回滚事务。(该接口主要面向使用Spring AOP进行声明式事务管理的场合)
-
编程式的事务管理与声明式事务处理
-
spring提供编程式的事务管理(自己创建事务管理器,由自己调用管理器的方法创建事务或处理事务,可以使用动态代理减少代理类的编写,但是还是要编写很多事务的代码在业务代码的前后,不是很方便管理,对于事务管理这种特性基本一致的操作用声明反而更利于维护)和声明式事务处理(声明后由容器创建,由声明来调用相应的方法,声明式事务管理用AOP避免了我们为每一个需要事务管理的类创建一个代理类)。
-
若是声明式事务管理,我们会发现,我们没有自己操作commit的空间,他会在你事务声明的方法结束时就commit,你可以把很多业务代码放在一个事务service方法中实现同一事务,但若是不想则可以编码式事务控制。
声明式事务管理中我们用到org.springframework.transaction.interceptor.TransactionInterceptor帮我们拦截业务方法,使得我们在springMVc中调用serviece中的方法时(我们从容器拿到的Service是经过AOP(也就是动态代理)处理的了),需要先经过它。
需要一个容器去放置用不同ORM框架时,在TransactionInterceptor帮我们拦截后,将service增强成我们想要的模板,加入到像JdbcTempalte/SqlsessionTemplate这样不同的模板中,以便在调用service的方法时,自动去调用相应的数据库框架执行sql语句时需要的流程,如SqlsessionTemplate(它的原理跟sqlsession.getMapper相似)会自动对sqlsession进行创建,再进行该框架对sql语句执行流程,这个容器可以是ProxyFactory(ProxyFactoryBean)或者其他的interceptor的实现类
而sqlsessionTemplate所需要的springMapConfig已经用SqlSessionFactoryBean创建的SqlSessionFactory中了,sqlsessionTemplate会将SqlSessionFactory注入其中,便可以拥有需要的Dao层信息和需要的sql语句等了
在spring 1.x到2.x中我们可以使用4种配置的方式在IoC容器的配置文件种指定事务需要的元数据
-
使用ProxyFactory(ProxyFactoryBean)+TransactionIntercepter
<!-- TransactionInterceptor -->//拦截我们需要的service方法 <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager"> <ref bean="transactionManager" /> </property> <property name="transactionAttributeSource"> <value> org.springframework.prospring.ticket.service.PaymentService.transfer=PROPAGATION_REQUIRED,ISOLATION_SERIALIZABLE,timeout_30,-Exception </value> </property> </bean> <!-- Business Object --> <bean id="paymentServiceTarget" class="org.springframework.prospring.ticket.service.PaymentServiceImpl"> <property name="paymentDao"> <ref local="paymentDao" /> </property> </bean> // 将需要的框架Template/相应的dao实现,与Service层接口以及transactionInterceptor集成一个Bean方便Client调用 <!-- Transactional proxy for the primary business object --> <bean id="paymentService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref local="paymentServiceTarget" /> </property> <property name="proxyInterfaces"> <value>org.springframework.prospring.ticket.service.PaymentService </value> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean>
-
使用“一站式”的TransactionProxyFactoryBean
-
使用BeanNameAutoProxyCreater
-
使用Spring2.x的声明事务配置方式(我们使用的)
<context:component-scan base-package="cn.itcast"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm"/> <property name="user" value="root"/> <property name="password" value="123456"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="comboPooledDataSource"/> </bean> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.itcast.dao"/> </bean> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> // <tx:advice>专门为声明事务Advice而设置的配置元素,底层还是TransactionInterceptor 若不指定<tx:attributes>,则采用DefaultTransactionDefinition <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager"> <tx:attributes> <tx:method name="find*" read-only="true"/> <tx:method name="*" isolation="DEFAULT"/> </tx:attributes> </tx:advice> // 作用如同ProxyFactoryBean一直,不过使用了aop实现,只需指定ServiceTarget的class,之后的便依靠动态代理实现 <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.itcast.service.impl.*ServiceImpl.*(..))"/> </aop:config> //这个实现使得我们在MVC层调用的service对象已经是经过代理实现了的,我们可以直接用其中的方法,底层都会依据流程自己完成,我们会发现,我们没有自己操作commit的空间,他会在你事务声明的方法结束时就commit,你可以把很多业务代码放在一个事务service方法中实现同一事务,但若是不想则可以编码式事务控制。
-
Spring的事务处理
-
我们一般通过TransactionTemplate来对PlatformTransactionManager的事务进行封装,使得出现异常需要callback时,将其依据callback接口的实现可以在TransactionTemplate类的excute方法中便处理成功,我们就只需要注意call back接口的设计与实现,就能将PlatformTransactionManager的callback都由该模板实现,减少重复代码的编写。当然小型测试直接用也可以。
-
Spring的事务处理中,通用的事务处理流程是由抽象事务管理器AbstractPlatformTransactionManager来提供的,而具体的底层事务处理实现,由PlatformTransactionManager的具体实现类来实现,如 DataSourceTransactionManager 、JtaTransactionManager和 HibernateTransactionManager等。(我们通常使用的是DataSourceTransactionManager来与mybatis集成)
-
spring事务处理的一个关键是保证在整个事务的生命周期里所有执行sql的jdbc connection和处理事务的jdbc connection始终是同一个(不然事务通过不同connection commit的时候可能会相互覆盖,顺序也难以确定)。然后执行sql的业务代码一般都分散在程序的不同地方,如何让它们共享一个jdbc connection呢?
-
这里spring做了一个前提假设:即一个事务的操作一定是在一个thread中执行,在事务未结束前该事务一直存在于该thread中,且一个thread中如果有多个事务在不同的jdbc connection的话,他们必须顺序执行(在上一个结束的情况下),不能同时存在,此时若是将connection也绑定到线程中,那(这个假设在绝大多数情况下都是成立的,mybatis自身的事务处理中,sqlsession也可以多次执行commit,connection的获取是在sqlsession执行sql时进行的,因此sqlsession也可有多个不同jdbc connection生成的事务,必须顺序执行)。
-
基于这个假设,spring在transaction创建时,会用ThreadLocal把创建这个事务的jdbc connection绑定到当前thread,接下来在事务的整个生命周期中都会从ThreadLocal中获取同一个jdbc connection(只要没有主动断开connection)。
-
Spring本身的事务流程(配合JdbcTemplate)
-
从上面可以看出,Spring事务管理一共可分为三个步骤,分别是初始化事务、提交事务、回滚事务,spring事务工作流相当于为用户屏蔽了具体orm框架的底层处理逻辑,基于spring开发的程序,即便更换了orm框架也是跟换了调用的sqlTemplate,我们的事务管理器更换成适合于他的便可,基本不用改变其他的实现。这是Spring的优点
-
Spring控制datasourcetransaction ,mybaitis用springManagedTransaction对数据库进行sql操作,但commit等都是最后由datasourceTransaction 进行.
SqlSessionFactoryBean
-
再看这个容器中的参数,无论如何我们都需要SqlSessionFactoryBean(无论是否有用Spring的事务管理都需要)和DataSourceTransactionManager
-
由SqlSessionFactoryBean创建工厂,此工厂将生成SpringManagedTransactionFactory(可能有人会好奇为什么要大费周章重新实现一个TransactionFactory,到下面进入sqlsession的部分就可以知道答案了),再封装成 Environment 对象。最后封装成configuration对象来调用sqlSessionFactoryBuilder.build(configuration);
需要注意的是 SqlSessionFactoryBean 实现了 Spring 的 FactoryBean 接口。这意味着由 Spring 最终创建的 bean 并不是 SqlSessionFactoryBean 本身,而是工厂类(SqlSessionFactoryBean)的 getObject() 方法的返回结果。这种情况下,Spring 将会在应用启动时为你创建 SqlSessionFactory,并使用 sqlSessionFactory 这个名字存储起来。因为是spring容器生成的,所以xml解析时对实现这个接口的对象会自动调用getObject() 方法
等效的 Java 代码如下:
@Bean public SqlSessionFactory sqlSessionFactory() { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource()); return factoryBean.getObject(); }
-
生成sqlsqlSessionFactory,由XMLConfigBuilder读取parse成一个sqlsqlSessionFactory,交由SqlsessionTemplate使用(其实现了
SqlSession
接口,并不直接调用具体的SqlSession
的方法,而是委托给一个动态代理,通过代理SqlSessionInterceptor
对方法调用进行拦截,用调用接口的方法时,会去寻找是否有了停留在resources中的未关闭的sqlsession)若是编程式事务控制的话我们应该先创建SqlSessionFactoryBean创建sqlsqlSessionFactory,再将其注入自己创建的SqlsessionTemplate对象中,再利用SqlsessionTemplate进行增删查改
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { protected SqlSessionFactory buildSqlSessionFactory() throws IOException { XMLConfigBuilder xmlConfigBuilder = null; Configuration configuration; ··· 各种configuration的参数判断 ···· //生成SpringManagedTransactionFactory if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } //封装成 Environment 对象 //封装成configuration对象 configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); ··· 参数检查 ··· //调用sqlSessionFactoryBuilder.build(configuration);生成sqlsqlSessionFactory, return this.sqlSessionFactoryBuilder.build(configuration); } }
DataSourceTransactionManager
-
若要使用spring的事务,使用sqlsessionTemplate前,我们要分析DataSourceTransactionManager整个流程,我们需要一步一步来,从最顶层开始
-
Spring的PlatformTransactionManager是事务管理的顶层接口,其中定义的三个方法对应的就是初始化事务、提交事务、回滚事务,,然后AbstractPlatformTransactionManager抽象类(作为模板)给出了三个步骤的具体实现。
-
但对诸如doGetTransaction()之类的和doBegin等还是弄成了抽象方法由DataSourceTransactionManager等实现,以便自己决定对 TransactionSynchronization接口的实现类进行调用,自己实现线程安全、获得ConnectionHolder和创建新事务时按何种方式dobgin,连接方式和注入各种参数。
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { public final TransactionzhuangtStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException { //在DataSourceTransactionManager中重写了的类,查询当前线程中是否有传入的datasource对应的ConnectionHolder,有则依据他创建transaction Object transaction = this.doGetTransaction(); boolean debugEnabled = this.logger.isDebugEnabled(); if (definition == null) { definition = new DefaultTransactionDefinition(); } if (this.isExistingTransaction(transaction)) { return this.handleExistingTransaction((TransactionDefinition)definition, transaction, debugEnabled); } ··· 各种条件判断 ··· try { boolean newSynchronization = this.getTransactionSynchronization() != 2; //被创建的事务状态对象类型是DefaultTransactionStatus,它持有上述创建的事务对象。事务状态对象主要用于获取当前事务对象的状态,比如事务是否被标记了回滚,是否是一个新事务等等。 DefaultTransactionStatus status = this.newTransactionStatus((TransactionDefinition)definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); //事务开始处理,进行促使化,连接获取 this.doBegin(transaction, (TransactionDefinition)definition); //对象都交由TransactionSynchronizationManager管理,TransactionSynchronizationManager把这些对象都保存在ThreadLocal中。在该transaction未commit/关闭前,该线程就会与该connection一直绑定在一起,通过只能同时绑定一个connection的原理,实现事务管理。 this.prepareSynchronization(status, (TransactionDefinition)definition); 返回事务状态对象:(主要进行:查询事务状态、通过setRollbackOnly()方法标记当前事务使其回滚,根据事务参数创建内嵌事务),statu的意思是方便先对事务进行判断再获取transaction进行操作。 return status; } catch (Error | RuntimeException var7) { this.resume((Object)null, suspendedResources); throw var7; } } } }
- DataSourceTransactionManager(继承了AbstractPlatformTransactionManager)的主要作用是创建transaction和对transaction的初始化
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean { @Override//主要增加了对TransactionSynchronizationManager中当前线程是否有connectionHolder protected Object doGetTransaction() { // 创建事务对象 DataSourceTransactionObject txObject = new DataSourceTransactionObject(); txObject.setSavepointAllowed(isNestedTransactionAllowed()); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); //ConnectionHolder赋值给事务对象txObject txObject.setConnectionHolder(conHolder, false); return txObject; } } //处理事务开始的方法 protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; Connection con = null; try { //如果数据源事务对象的ConnectionHolder为null或者是事务同步的 if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { //获取当前数据源的数据库连接 Connection newCon = this.dataSource.getConnection(); if (logger.isDebugEnabled()) { logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); } //为数据源事务对象设置ConnectionHolder txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } //设置数据源事务对象的事务同步 txObject.getConnectionHolder().setSynchronizedWithTransaction(true); //获取数据源事务对象的数据库连接 con = txObject.getConnectionHolder().getConnection(); //根据数据连接和事务属性,获取数据库连接的事务隔离级别 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); //为数据源事务对象设置事务隔离级别 txObject.setPreviousIsolationLevel(previousIsolationLevel); //如果数据库连接设置了自动事务提交属性,则关闭自动提交 if (con.getAutoCommit()) { //保存数据库连接设置的自动连接到数据源事务对象中 txObject.setMustRestoreAutoCommit(true); if (logger.isDebugEnabled()) { logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); } //设置数据库连接自动事务提交属性为false,即禁止自动事务提交 con.setAutoCommit(false); } //激活当前数据源事务对象的事务配置 txObject.getConnectionHolder().setTransactionActive(true); //获取事务配置的超时时长 int timeout = determineTimeout(definition); //如果事务配置的超时时长不等于事务的默认超时时长 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { //数据源事务对象设置超时时长 txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } //把当前数据库Connection和线程ThreadLocal绑定(key为DataSource,value为getConnectionHolder)spring事务管理的关键一步 if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder()); } } catch (Exception ex) { DataSourceUtils.releaseConnection(con, this.dataSource); throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); } }
TransactionSynchronizationManager
- 主要从线程中根据键值获取资源
public abstract class TransactionSynchronizationManager {
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");
//获取相应的资源,键值先unwrapResourceIfNecessary处理,交由doget获取
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;
}
//从保存在线程的resource获取
@Nullable
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = (Map)resources.get();
if (map == null) {
return null;
} else {
Object value = map.get(actualKey);
if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
map.remove(actualKey);
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
}
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 = (Map)resources.get();
if (map == null) {
map = new HashMap();
resources.set(map);
}
Object oldValue = ((Map)map).put(actualKey, value);
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() + "]");
} else {
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + Thread.currentThread().getName() + "]");
}
}
}
- 此时若是注释运行的便需要把PlatformTransactionManager,他生成的TransactionStatus、TransactionAttribute等封装到TransactionInfo中以便注解能自己调用
Spring+mybatis
-
完成了对事务的创建,并将其用TransactionSynchronizationManager绑定到了thread local上,接着便是对sqlsessionTemplate的调用了即对mybatis框架的sqlsession的整合了
-
Spring+mybatis的事务控制流程
SqlsessionTemplate
- SqlsessionTemplate的使用并不是直接用sqlsession进行增删查改(实际上是sqlsession的一个代理类,其实现了
SqlSession
接口,并不直接调用具体的SqlSession
的方法,而是委托给一个动态代理,通过代理SqlSessionInterceptor
对方法调用进行拦截,用调用接口的方法时,会去寻找是否有了停留在resources中的未关闭的sqlsession)
public class SqlSessionTemplate implements SqlSession, DisposableBean {
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
// 动态代理中的 InvocationHandler类,真正对方法的前后进行通知的类,代理类对象只是提供一个目标对象的封装以调用而已
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//查看是否有为关闭的闲置的同个配置的sqlsession,有则调用,这也是你用sqlsessionTemplate代理执行增删查改一直是同一个sqlsession完成事务管理的原因,没有则生成。我们可以看到是由SqlSessionUtils实现的
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
//查询是否自动提交
//若是自动则为一条sql一次commit,是为新手设置的,一般手动提交。
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
//关闭sqlsession,同一个事务可以有多个sqlsession
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
}
SqlSessionUtils
- 我们来看下SqlSessionUtils的实现,这里的sqlsession的创建需要注意transactionFactory是SpringManageTransactionFactory。
public final class SqlSessionUtils {
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
Assert.notNull(executorType, "No ExecutorType specified");
//有相应的sqlsessionHolder则取出里面的SqlSession
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
//没有则用工厂创建sqlsession,注意:此时的sqlsessionfactory是sqlsessionfactoryBean创建的,即他的configuration中的Enviroment中的transactionFactory是SpringManageTransactionFactory。
session = sessionFactory.openSession(executorType);
// 注册sqlsession到TransactionSyncronizedMager,
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
}
}
-
关于SqlSessionUtils中的registerSessionHolder方法,注册sqlsession到TransactionSyncronizedMager中,
-
包含两个方法
bindResource(sessionFactory, holder);
registerSynchronization(new SqlSessionUtils.SqlSessionSynchronization(holder, sessionFactory));
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
//从sessionFactory中获取Environment
Environment environment = sessionFactory.getConfiguration().getEnvironment();
//判断environment中封装的TransactionFactorySpringManagedTransactionFactory,是否用到了Spring的事务管理服务,sqlsession中对TransactionSyncronizedMager的应用只有与Spring集成时才用到,mybatis自身的事务管理并不会用到。
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
}
SqlSessionHolder holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
//绑定、注册到TransactionSynchronizationManager
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(new SqlSessionUtils.SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) != null) {
throw new TransientDataAccessResourceException("SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
}
}
} else if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
}
}
SqlSessionSynchronization
-
其中SqlSessionSynchronization是一个事务生命周期的callback接口,mybatis-spring通过SqlSessionSynchronization在事务提交和回滚前分别调用DefaultSqlSession.commit()和DefaultSqlSession.rollback()
private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter { public void beforeCommit(boolean readOnly) { if (TransactionSynchronizationManager.isActualTransactionActive()) { try { if (SqlSessionUtils.LOGGER.isDebugEnabled()) { SqlSessionUtils.LOGGER.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]"); } this.holder.getSqlSession().commit(); } catch (PersistenceException var4) { if (this.holder.getPersistenceExceptionTranslator() != null) { DataAccessException translated = this.holder.getPersistenceExceptionTranslator().translateExceptionIfPossible(var4); if (translated != null) { throw translated; } } throw var4; } } } public void beforeCompletion() { if (!this.holder.isOpen()) { if (SqlSessionUtils.LOGGER.isDebugEnabled()) { SqlSessionUtils.LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); } TransactionSynchronizationManager.unbindResource(this.sessionFactory); this.holderActive = false; if (SqlSessionUtils.LOGGER.isDebugEnabled()) { SqlSessionUtils.LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]"); } this.holder.getSqlSession().close(); } } public void afterCompletion(int status) { if (this.holderActive) { if (SqlSessionUtils.LOGGER.isDebugEnabled()) { SqlSessionUtils.LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); } TransactionSynchronizationManager.unbindResourceIfPossible(this.sessionFactory); this.holderActive = false; if (SqlSessionUtils.LOGGER.isDebugEnabled()) { SqlSessionUtils.LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]"); } this.holder.getSqlSession().close(); } this.holder.reset(); }
datasourceUtils和SpringManagedTransaction
- 一番周折终于拿到了sqlsession到了代理类SqlsessionTemplate中,接着便是目标代码本身(sql语句)的执行了
- 在此之前我们谈及为何要专门用SpringManagedTransaction,现在就要揭晓了,之前我们创建了sqlsession到了SpringTemplate,但是还未获取connection,所以我们执行语句前就要获取connection,我们用mybatis的Executor执行语句,会在prepareStatement中获得connection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = this.transaction.getConnection();
return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}
-
看到上面的transsaction便可以知道我们需要一个transaction来实现连接的获取,但是我们的连接是之前绑定到了ThreadLocal中了,我们Spring事务管理的关键要让此时Thread中的connection用上,这时候我们就需要用到DataSourceUtils来获取了
-
datasourceUtils:也是从TransactionSynchronizationManager获取connection
-
SqlSessionUtils从TransactionSynchronizationManager获取ConnectionHolder交给SpringManagedTransaction,并作为参数封装进Executor,最后封装进DefaultSqlSession,将Spring的事务管理与它的数据访问框架是紧密结合的
-
SpringManagedTransaction中保留由datasource等信息,而且它特别实现了对SqlSessionUtils的调用(其他的jdbcTransaction和managedTransaction都没有对SqlSessionUtils调用的方法,这两者只适用于mybaitis自身事务情况,不适用于集成)
-
并且可以用来给datasourceUtils提供参数,让其可以直接从TransactionSynchronizationManager中调用相应的connection,因为是同一线程且事务没结束所以能保证是同一个了
-
SpringManagedTransaction的意义便在此,我们的问题也就解决了,最后便是jdbc.connection的excute操作了
public abstract class DataSourceUtils { public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(fetchConnection(dataSource)); } return conHolder.getConnection(); } // Else we either got no holder or an empty thread-bound holder here. logger.debug("Fetching JDBC Connection from DataSource"); Connection con = fetchConnection(dataSource); if (TransactionSynchronizationManager.isSynchronizationActive()) { logger.debug("Registering transaction synchronization for JDBC Connection"); // Use same Connection for further JDBC actions within the transaction. // Thread-bound object will get removed by synchronization at transaction completion. ConnectionHolder holderToUse = conHolder; if (holderToUse == null) { holderToUse = new ConnectionHolder(con); } else { holderToUse.setConnection(con); } holderToUse.requested(); TransactionSynchronizationManager.registerSynchronization( new ConnectionSynchronization(holderToUse, dataSource)); holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != conHolder) { TransactionSynchronizationManager.bindResource(dataSource, holderToUse); } } return con; }
- 可以看到mybatis-spring处理事务的主要流程和spring jdbc处理事务并没有什么区别,都是通过DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事务的生命周期管理,而且jdbc connection的创建也是通过DataSourceTransactionManager.getTransaction()完成,mybatis并没有参与其中,mybatis只是在执行sql时通过DataSourceUtils.getConnection()获得当前thread的jdbc connection,然后在其上执行sql。
commit
-
最后写一下commit的流程(rollback实现差不多,回到保存的回调点而已)
-
调用DataSourceTransactionManager的父类AbstractPlatformTransactionManager的commit
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
public final void commit(TransactionStatus status) throws TransactionException {
···
status 是否已完成或是否需要rollback
···
//正常流程会调用processCommit
this.processCommit(defStatus);
}
}
}
}
```
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
boolean unexpectedRollback = false;
//commit准备
this.prepareForCommit(status);
//BeforeCommit:commit前对springTransaciton、sqlsession的commit,大部分未实现这个的功能
this.triggerBeforeCommit(status);
//BeforeCompletion:Complete前对springTransaciton、sqlsession的commit
this.triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
···
是否有记录回调点/是否需要回调
···
// 在DataSourceTransactionManager中实现对connection的commit
this.doCommit(status);
···
异常处理
···
try {
//对TransactionSynchronizationManager中的synchronizations用Iterator计数器遍历删除
this.triggerAfterCommit(status);
} finally {
// 若上一步没清理完成,则再次清理一次,用synchronizations用Iterator计数器遍历删除
this.triggerAfterCompletion(status, 0);
}
} finally {
// 清空TransactionSynchronizationManager,若有挂起的事务,则恢复执行
this.cleanupAfterCompletion(status);
}
}
// 清空TransactionSynchronizationManager,若有挂起的事务,则恢复执行,这和definiton设置的事务级别有关
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
status.setCompleted();
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (status.isNewTransaction()) {
this.doCleanupAfterCompletion(status.getTransaction());
}
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
this.logger.debug("Resuming suspended transaction after completion of inner transaction");
}
Object transaction = status.hasTransaction() ? status.getTransaction() : null;
//SuspendedResourcesHolder用静态方法放置在JVM的方法区,可以直接调用
this.resume(transaction, (AbstractPlatformTransactionManager.SuspendedResourcesHolder)status.getSuspendedResources());
}
- 这便是整个spring与mybatis集成的事务控制了。
- 这里提个建议,如果有好的借鉴理解的书或其他资源要参照着学习会效果更好,我写完后发现Spring揭秘一书中有许多细节仍是需要注意,对于Spring的讲解也更有系统,可惜自己一直只顾着看源码忘了