Spring 与 MyBatis 事务管理源码解析
Posted eternal-heathens
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 与 MyBatis 事务管理源码解析相关的知识,希望对你有一定的参考价值。
- 用到mybatis便由spring和myabtis集成,SqlSessionFactoryBean(直接负责对mybatis所需环境的创建) ,配置相应的datasource到springConfig文件中,并将datasource注入到SqlSessionFactoryBean的实例化到容器中,依据他创建sqlsession到spring容器中,便可调用sqlssion执行对数据库的操作
(以下的三个都由Spring 的容器ClassPathXmlApplicationContext进行管理)
<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>
- 在对@Repository的类的接口类调用时,MapperScannerConfigurer将Dao层加到Spring容器中,以便Service层调用
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方法用以注入
···
}
-
获取事务属性对象(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方法中实现同一事务,但若是不想则可以编码式事务控制。
-
-
我们一般通过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 进行.
<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>
-
再看这个容器中的参数,无论如何我们都需要SqlSessionFactoryBean(无论是否有用Spring的事务管理都需要)和DataSourceTransactionManager
-
由SqlSessionFactoryBean创建工厂,此工厂将生成SpringManagedTransactionFactory(可能有人会好奇为什么要大费周章重新实现一个TransactionFactory,到下面进入sqlsession的部分就可以知道答案了),再封装成 Environment 对象。最后封装成configuration对象来调用sqlSessionFactoryBuilder.build(configuration);生成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)); if (!ObjectUtils.isEmpty(this.mapperLocations)) { Resource[] var29 = this.mapperLocations; var27 = var29.length; for(var5 = 0; var5 < var27; ++var5) { Resource mapperLocation = var29[var5]; if (mapperLocation != null) { try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception var20) { throw new NestedIOException("Failed to parse mapping resource: ‘" + mapperLocation + "‘", var20); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: ‘" + mapperLocation + "‘"); } } } } else if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property ‘mapperLocations‘ was not specified or no matching resources found"); } //调用sqlSessionFactoryBuilder.build(configuration);生成sqlsqlSessionFactory, return this.sqlSessionFactoryBuilder.build(configuration); } }
-
若要使用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); ? ? ? ? ? } ? ? ? } ?
-
此时若是注释运行的便需要把PlatformTransactionManager,他生成的TransactionStatus、TransactionAttribute等封装到TransactionInfo中以便注解能自己调用
-
完成了对事务的创建,并将其用TransactionSynchronizationManager绑定到了thread local上,接着便是对sqlsessionTemplate的调用了即对mybatis框架的sqlsession的整合了
-
Spring+mybatis的事务控制流程
- SqlsessionTemplate(如同sqlsession.getmapper获得的代理对象)的使用并不是直接用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的实现,这里的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是一个事务生命周期的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(); }
-
一番周折终于拿到了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的流程(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的讲解也更有系统,可惜自己一直只顾着看源码忘了
以上是关于Spring 与 MyBatis 事务管理源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Mybaits 源码解析 ----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
Mybatis源码解析-mybatis-spring原理解析