Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
不知道一些同学有没有这种疑问,为什么mybtis中要配置datasource,spring的事务中也要配置datasource?那么mybatis和spring事务中用的connection是同一个吗?我们常用配置如下
<!--会话工厂 --> <bean id="sqlsessionfactory" class="org.mybatis.spring.sqlsessionfactorybean"> <property name="datasource" ref="datasource" /> </bean> <!--spring事务管理 --> <bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager"> <property name="datasource" ref="datasource" /> </bean> <!--使用注释事务 --> <tx:annotation-driven transaction-manager="transactionmanager" />
看到没,sqlsessionfactory中配置了datasource,transactionmanager也配置了datasource,我们来回忆一下sqlsessionfactorybean这个类
1 protected sqlsessionfactory buildsqlsessionfactory() throws ioexception { 2 3 // 配置类 4 configuration configuration; 5 // 解析mybatis-config.xml文件, 6 // 将相关配置信息保存到configuration 7 xmlconfigbuilder xmlconfigbuilder = null; 8 if (this.configuration != null) { 9 configuration = this.configuration; 10 if (configuration.getvariables() == null) { 11 configuration.setvariables(this.configurationproperties); 12 } else if (this.configurationproperties != null) { 13 configuration.getvariables().putall(this.configurationproperties); 14 } 15 //资源文件不为空 16 } else if (this.configlocation != null) { 17 //根据configlocation创建xmlconfigbuilder,xmlconfigbuilder构造器中会创建configuration对象 18 xmlconfigbuilder = new xmlconfigbuilder(this.configlocation.getinputstream(), null, this.configurationproperties); 19 //将xmlconfigbuilder构造器中创建的configuration对象直接赋值给configuration属性 20 configuration = xmlconfigbuilder.getconfiguration(); 21 } 22 23 //略.... 24 25 if (xmlconfigbuilder != null) { 26 try { 27 //解析mybatis-config.xml文件,并将相关配置信息保存到configuration 28 xmlconfigbuilder.parse(); 29 if (logger.isdebugenabled()) { 30 logger.debug("parsed configuration file: '" + this.configlocation + "'"); 31 } 32 } catch (exception ex) { 33 throw new nestedioexception("failed to parse config resource: " + this.configlocation, ex); 34 } 35 } 36 37 if (this.transactionfactory == null) { 38 //事务默认采用springmanagedtransaction,这一块非常重要 39 this.transactionfactory = new springmanagedtransactionfactory(); 40 } 41 // 为sqlsessionfactory绑定事务管理器和数据源 42 // 这样sqlsessionfactory在创建sqlsession的时候可以通过该事务管理器获取jdbc连接,从而执行sql 43 configuration.setenvironment(new environment(this.environment, this.transactionfactory, this.datasource)); 44 // 解析mapper.xml 45 if (!isempty(this.mapperlocations)) { 46 for (resource mapperlocation : this.mapperlocations) { 47 if (mapperlocation == null) { 48 continue; 49 } 50 try { 51 // 解析mapper.xml文件,并注册到configuration对象的mapperregistry 52 xmlmapperbuilder xmlmapperbuilder = new xmlmapperbuilder(mapperlocation.getinputstream(), 53 configuration, mapperlocation.tostring(), configuration.getsqlfragments()); 54 xmlmapperbuilder.parse(); 55 } catch (exception e) { 56 throw new nestedioexception("failed to parse mapping resource: '" + mapperlocation + "'", e); 57 } finally { 58 errorcontext.instance().reset(); 59 } 60 61 if (logger.isdebugenabled()) { 62 logger.debug("parsed mapper file: '" + mapperlocation + "'"); 63 } 64 } 65 } else { 66 if (logger.isdebugenabled()) { 67 logger.debug("property 'mapperlocations' was not specified or no matching resources found"); 68 } 69 } 70 71 // 将configuration对象实例作为参数, 72 // 调用sqlsessionfactorybuilder创建sqlsessionfactory对象实例 73 return this.sqlsessionfactorybuilder.build(configuration); 74 }
我们看第39行,mybatis集成spring后,默认使用的transactionfactory是springmanagedtransactionfactory,那我们就来看看其获取transaction的方法
private sqlsession opensessionfromconnection(executortype exectype, connection connection) { try { boolean autocommit; try { autocommit = connection.getautocommit(); } catch (sqlexception e) { // failover to true, as most poor drivers // or databases won't support transactions autocommit = true; } //从configuration中取出environment对象 final environment environment = configuration.getenvironment(); //从environment中取出transactionfactory final transactionfactory transactionfactory = gettransactionfactoryfromenvironment(environment); //创建transaction final transaction tx = transactionfactory.newtransaction(connection); //创建包含事务操作的执行器 final executor executor = configuration.newexecutor(tx, exectype); //构建包含执行器的sqlsession return new defaultsqlsession(configuration, executor, autocommit); } catch (exception e) { throw exceptionfactory.wrapexception("error opening session. cause: " + e, e); } finally { errorcontext.instance().reset(); } } private transactionfactory gettransactionfactoryfromenvironment(environment environment) { if (environment == null || environment.gettransactionfactory() == null) { return new managedtransactionfactory(); } //这里返回springmanagedtransactionfactory return environment.gettransactionfactory(); } @override public transaction newtransaction(datasource datasource, transactionisolationlevel level, boolean autocommit) { //创建springmanagedtransaction return new springmanagedtransaction(datasource); }
springmanagedtransaction
也就是说mybatis的执行事务的事务管理器就切换成了springmanagedtransaction,下面我们再去看看springmanagedtransactionfactory类的源码:
public class springmanagedtransaction implements transaction { private static final log logger = logfactory.getlog(springmanagedtransaction.class); private final datasource datasource; private connection connection; private boolean isconnectiontransactional; private boolean autocommit; public springmanagedtransaction(datasource datasource) { assert.notnull(datasource, "no datasource specified"); this.datasource = datasource; } public connection getconnection() throws sqlexception { if (this.connection == null) { this.openconnection(); } return this.connection; } private void openconnection() throws sqlexception { //通过datasourceutils获取connection,这里和jdbctransaction不一样 this.connection = datasourceutils.getconnection(this.datasource); this.autocommit = this.connection.getautocommit(); this.isconnectiontransactional = datasourceutils.isconnectiontransactional(this.connection, this.datasource); if (logger.isdebugenabled()) { logger.debug("jdbc connection [" + this.connection + "] will" + (this.isconnectiontransactional ? " " : " not ") + "be managed by spring"); } } public void commit() throws sqlexception { if (this.connection != null && !this.isconnectiontransactional && !this.autocommit) { if (logger.isdebugenabled()) { logger.debug("committing jdbc connection [" + this.connection + "]"); } //通过connection提交,这里和jdbctransaction一样 this.connection.commit(); } } public void rollback() throws sqlexception { if (this.connection != null && !this.isconnectiontransactional && !this.autocommit) { if (logger.isdebugenabled()) { logger.debug("rolling back jdbc connection [" + this.connection + "]"); } //通过connection回滚,这里和jdbctransaction一样 this.connection.rollback(); } } public void close() throws sqlexception { datasourceutils.releaseconnection(this.connection, this.datasource); } public integer gettimeout() throws sqlexception { connectionholder holder = (connectionholder)transactionsynchronizationmanager.getresource(this.datasource); return holder != null && holder.hastimeout() ? holder.gettimetoliveinseconds() : null; } }
org.springframework.jdbc.datasource.datasourceutils#getconnection
public static connection getconnection(datasource datasource) throws cannotgetjdbcconnectionexception { try { return dogetconnection(datasource); } catch (sqlexception ex) { throw new cannotgetjdbcconnectionexception("could not get jdbc connection", ex); } } public static connection dogetconnection(datasource datasource) throws sqlexception { assert.notnull(datasource, "no datasource specified"); //transactionsynchronizationmanager重点!!!有没有很熟悉的感觉?? //还记得我们前面spring事务源码的分析吗?@transaction会创建connection,并放入threadlocal中 //这里从threadlocal中获取connectionholder connectionholder conholder = (connectionholder)transactionsynchronizationmanager.getresource(datasource); if (conholder == null || !conholder.hasconnection() && !conholder.issynchronizedwithtransaction()) { logger.debug("fetching jdbc connection from datasource"); //如果没有使用@transaction,那调用mapper接口方法时,也是通过spring的方法获取connection connection con = fetchconnection(datasource); if (transactionsynchronizationmanager.issynchronizationactive()) { logger.debug("registering transaction synchronization for jdbc connection"); connectionholder holdertouse = conholder; if (conholder == null) { holdertouse = new connectionholder(con); } else { conholder.setconnection(con); } holdertouse.requested(); transactionsynchronizationmanager.registersynchronization(new datasourceutils.connectionsynchronization(holdertouse, datasource)); holdertouse.setsynchronizedwithtransaction(true); if (holdertouse != conholder) { //将获取到的connectionholder放入threadlocal中,那么当前线程调用下一个接口,下一个接口使用了spring事务,那spring事务也可以直接取到mybatis创建的connection //通过threadlocal保证了同一线程中spring事务使用的connection和mapper代理类使用的connection是同一个 transactionsynchronizationmanager.bindresource(datasource, holdertouse); } } return con; } else { conholder.requested(); if (!conholder.hasconnection()) { logger.debug("fetching resumed jdbc connection from datasource"); conholder.setconnection(fetchconnection(datasource)); } //所以如果我们业务代码使用了@transaction注解,在spring中就已经通过datasource创建了一个connection并放入threadlocal中 //那么当mapper代理对象调用方法时,通过sqlsession的springmanagedtransaction获取连接时,就直接获取到了当前线程中spring事务创建的connection并返回 return conholder.getconnection(); } }
想看怎么获取connholder
org.springframework.transaction.support.transactionsynchronizationmanager#getresource
//保存数据库连接的threadlocal private static final threadlocal<map<object, object>> resources = new namedthreadlocal<>("transactional resources"); @nullable public static object getresource(object key) { object actualkey = transactionsynchronizationutils.unwrapresourceifnecessary(key); //获取connectionholder object value = dogetresource(actualkey); .... return value; } @nullable private static object dogetresource(object actualkey) { /** * 从threadlocal <map<object, object>>中取出来当前线程绑定的map * map里面存的是<datasource,connectionholder> */ map<object, object> map = resources.get(); if (map == null) { return null; } //map中取出来对应datasource的connectionholder 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; }
我们看到直接从threadlocal中取出来的conn,而spring自己的事务也是操作的这个threadlocal中的conn来进行事务的开启和回滚,由此我们知道了在同一线程中spring事务中的connection和mybaits中mapper代理对象中操作数据库的connection是同一个,当取出来的conn为空时候,调用org.springframework.jdbc.datasource.datasourceutils#fetchconnection获取,然后把从数据源取出来的连接返回
private static connection fetchconnection(datasource datasource) throws sqlexception { //从数据源取出来conn connection con = datasource.getconnection(); if (con == null) { throw new illegalstateexception("datasource returned null from getconnection(): " + datasource); } return con; }
我们再来回顾一下上篇文章中的sqlsessioninterceptor
1 private class sqlsessioninterceptor implements invocationhandler { 2 private sqlsessioninterceptor() { 3 } 4 5 public object invoke(object proxy, method method, object[] args) throws throwable { 6 sqlsession sqlsession = sqlsessionutils.getsqlsession(sqlsessiontemplate.this.sqlsessionfactory, sqlsessiontemplate.this.executortype, sqlsessiontemplate.this.exceptiontranslator); 7 8 object unwrapped; 9 try { 10 object result = method.invoke(sqlsession, args); 11 // 如果当前操作没有在一个spring事务中,则手动commit一下 12 // 如果当前业务没有使用@transation,那么每次执行了mapper接口的方法直接commit 13 // 还记得我们前面讲的mybatis的一级缓存吗,这里一级缓存不能起作用了,因为每执行一个mapper的方法,sqlsession都提交了 14 // sqlsession提交,会清空一级缓存 15 if (!sqlsessionutils.issqlsessiontransactional(sqlsession, sqlsessiontemplate.this.sqlsessionfactory)) { 16 sqlsession.commit(true); 17 } 18 19 unwrapped = result; 20 } catch (throwable var11) { 21 unwrapped = exceptionutil.unwrapthrowable(var11); 22 if (sqlsessiontemplate.this.exceptiontranslator != null && unwrapped instanceof persistenceexception) { 23 sqlsessionutils.closesqlsession(sqlsession, sqlsessiontemplate.this.sqlsessionfactory); 24 sqlsession = null; 25 throwable translated = sqlsessiontemplate.this.exceptiontranslator.translateexceptionifpossible((persistenceexception)unwrapped); 26 if (translated != null) { 27 unwrapped = translated; 28 } 29 } 30 31 throw (throwable)unwrapped; 32 } finally { 33 if (sqlsession != null) { 34 sqlsessionutils.closesqlsession(sqlsession, sqlsessiontemplate.this.sqlsessionfactory); 35 } 36 37 } 38 return unwrapped; 39 } 40 }
看第15和16行,如果我们没有使用@transation,mapper方法执行完后,sqlsession将会提交,也就是说通过org.springframework.jdbc.datasource.datasourceutils#fetchconnection获取到的connection将会commit,相当于connection是自动提交的,也就是说如果不使用@transation,mybatis将没有事务可言。
如果使用了@transation呢?那在调用mapper代理类的方法之前就已经通过spring的事务生成了connection并放入threadlocal,并且设置事务不自动提交,当前线程多个mapper代理对象调用数据库操作方法时,将从threadlocal获取spring创建的connection,在所有的mapper方法调用完后,spring事务提交或者回滚,到此mybatis的事务是怎么被spring管理的就显而易见了
还有文章开头的问题,为什么mybtis中要配置datasource,spring的事务中也要配置datasource?
因为spring事务在没调用mapper方法之前就需要开一个connection,并设置事务不自动提交,那么transactionmanager中自然要配置datasource。那如果我们的service没有用到spring事务呢,难道就不需要获取数据库连接了吗?当然不是,此时通过springmanagedtransaction调用org.springframework.jdbc.datasource.datasourceutils#getconnection#fetchconnection方法获取,并将datasource作为参数传进去,实际上获取的connection都是通过datasource来获取的。