不知道一些同學有沒有這種疑問,為什么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將沒有事務可言。
Mybatis和Spring整合后SpringManagedTransaction和Spring的Transaction的關系:
- 如果開啟Spring事務,則先有Spring的Transaction,然后mybatis創建sqlSession時,會創建SpringManagedTransaction並加入sqlSession中,SpringManagedTransaction中的connection會從Spring的Transaction創建的Connection並放入ThreadLocal中獲取
- 如果沒有開啟Spring事務或者第一個方法沒有事務后面的方法有事務,則SpringManagedTransaction創建Connection並放入ThreadLocal中
spring結合mybatis后mybaits一級緩存失效分為兩種情況:
- 如果沒有開啟事務,每一次sql都是用的新的SqlSession,這時mybatis的一級緩存是失效的。
- 如果有事務,同一個事務中相同的查詢使用的相同的SqlSessioon,此時一級緩存是生效的。
如果使用了@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來獲取的。