不知道一些同窗有沒有這種疑問,爲何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來獲取的。