不知道一些同窗有没有这种疑问,为何Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗?咱们经常使用配置以下spring
<!--会话工厂 --> <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这个类sql
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); }
也就是说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; } }
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 session
//保存数据库链接的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获取,而后把从数据源取出来的链接返回mybatis
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; }
咱们再来回顾一下上篇文章中的SqlSessionInterceptorapp
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将没有事务可言。ide
若是使用了@Transation呢?那在调用Mapper代理类的方法以前就已经经过Spring的事务生成了Connection并放入ThreadLocal,而且设置事务不自动提交,当前线程多个Mapper代理对象调用数据库操做方法时,将从ThreadLocal获取Spring建立的connection,在全部的Mapper方法调用完后,Spring事务提交或者回滚,到此mybatis的事务是怎么被spring管理的就显而易见了fetch
还有文章开头的问题,为何Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?ui
由于Spring事务在没调用Mapper方法以前就须要开一个Connection,并设置事务不自动提交,那么transactionManager中天然要配置dataSource。那若是咱们的Service没有用到Spring事务呢,难道就不须要获取数据库链接了吗?固然不是,此时经过SpringManagedTransaction调用org.springframework.jdbc.datasource.DataSourceUtils#getConnection#fetchConnection方法获取,并将dataSource做为参数传进去,实际上获取的Connection都是经过dataSource来获取的。