mybatis做爲持久層流行框架已經被不少產品使用,固然爲了接入Spring這個業內的另外一個流行框架,mybatis仍是作了些事,經過分析除了明白支持Spring的機制原理還了解Spring對持久層接入留了那些口。java
<!-- 配置SqlSessionFactoryBean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:**/dao/**/*.xml"/> <property name="configLocation" value="classpath:spring/mybatis-config.xml" /> </bean> <!-- 掃描Dao類工具 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.**.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>
public interface UserDao { void save(User user); User query(String id); } @Service public class UserService { @Autowired private UserDao userDao; public void saveUser(User user) { userDao.save(user); } }
<mapper namespace="com.ss.dao.UserDao"> <select id="save" resultType="com.ss.dto.User"> select .... </select> </mapper>
這裏對應 UserDao 的 sqlmap就省略具體sql了。spring
XML定義完兩個Bean後,可見平常開發只須要添加Dao接口,以及對應的sqlmap,而後在調用的Service中就能夠自動注入,很是方便。sql
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>
SqlSessionFactoryBean 用於生產 SqlSessionFactory 的 FactoryBean編程
那麼,SqlSessionFactory 有什麼用? 若是沒有使用Spring,那麼咱們怎麼使用mybatis,以下:緩存
SqlSession sqlSession = sqlSessionFactory.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class);
原來是用於openSession() 返回 SqlSession 的。安全
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor
從實現的接口能夠看出,多半用於處理 BeanDefinition 的,該接口須要實現下面的方法。session
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
MapperScannerConfigurer 的實現源碼mybatis
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if(this.processPropertyPlaceHolders) { this.processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // 省略部分 code ... // 最主要是下面的 scan 定義的basePackage scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n")); }
即掃描配置basePackage中dao接口類,而後對掃描結果 beanDefinitions 進行處理app
public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 調用父類進行掃描 Set beanDefinitions = super.doScan(basePackages); if(beanDefinitions.isEmpty()) { this.logger.warn("No MyBatis mapper was found in \'" + Arrays.toString(basePackages) + "\' package. Please check your configuration."); } else { // 對結果進行處理 this.processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
因此,主要的邏輯都集中在 processBeanDefinitions() 這個方法框架
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { Iterator i$ = beanDefinitions.iterator(); while(i$.hasNext()) { BeanDefinitionHolder holder = (BeanDefinitionHolder)i$.next(); GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition(); if(this.logger.isDebugEnabled()) { this.logger.debug("Creating MapperFactoryBean with name \'" + holder.getBeanName() + "\' and \'" + definition.getBeanClassName() + "\' mapperInterface"); } // 這邊使用的招數叫【偷樑換柱】, 將原來的 class 換成了 MapperFactoryBean, 還給它設置了須要的參數 definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", Boolean.valueOf(this.addToConfig)); // 下面對 SqlSessionFactory 的引入處理 // 相關 code 省略 } }
就是說最終經過 MapperFactoryBean 的 getObject() 來生成Dao接口的實例,而後Service中 @Autowired 獲取到的就是該實例,至於爲何?由於實現 FactoryBean 接口。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { //其餘 code ... public T getObject() throws Exception { return this.getSqlSession().getMapper(this.mapperInterface); } public Class<T> getObjectType() { return this.mapperInterface; } public boolean isSingleton() { return true; } }
到這裏自動注入的祕密已經揭開,而後它怎麼經過
this.getSqlSession().getMapper(this.mapperInterface)
來返回代理對象的,基本上也就是動態代理那套東西,感興趣的能夠翻閱 mybatis源碼分析之mapper動態代理 寫得蠻詳細的。
說到持久層,那麼事務管理不能避免,mybatis是怎麼樣跟Spring的事務管理結合到完美無缺的,下面分析。
上一章中提到,方法
public T getObject() throws Exception { return this.getSqlSession().getMapper(this.mapperInterface); }
這裏 getSqlSession() 仍是咱們所知道的那個 DefaultSqlSession 麼,顯然不是了
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if(!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } }
當 set 進時已經被包裝了,因此真實都是調用 SqlSessionTemplate 的方法,SqlSessionTemplate 的密碼都藏在它的構造方法中:
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; // 生成了一個 SqlSession 的代理,調用 SqlSessionTemplate 的方法其實都轉調了 sqlSessionProxy 這個代理 this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor()); } public int insert(String statement) { return this.sqlSessionProxy.insert(statement); }
既然是動態代理,那麼處理邏輯就都在那個 InvocationHandler 的實現中
private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 獲取 sqlSession SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); Object unwrapped; try { // 調用 sqlSession 執行方法 Object t = method.invoke(sqlSession, args); if(!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { sqlSession.commit(true); } unwrapped = t; } catch (Throwable var11) { unwrapped = ExceptionUtil.unwrapThrowable(var11); if(SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; DataAccessException translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped); if(translated != null) { unwrapped = translated; } } throw (Throwable)unwrapped; } finally { // close sqlsession if(sqlSession != null) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } return unwrapped; } }
簡化成
private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 獲取 sqlSession SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); // 真實執行方法 Object t = method.invoke(sqlSession, args); // close sqlSession SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); return unwrapped; } }
這樣就是典型的 around 結構。
這時,若是沒有事務管理框架的話,那麼必然須要本身向 DataSource 獲取 connection,而後根據須要開啓事務,最後再commit 事務。
可是,若是有事務管理框架的話,就須要向框架獲取 connection,由於這時事務可能已經被框架生成的代理開啓了。
mybatis 也遵守這種處理方式,跟蹤源碼。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { Assert.notNull(sessionFactory, "No SqlSessionFactory specified"); Assert.notNull(executorType, "No ExecutorType specified"); // TransactionSynchronizationManager.getResource 的源碼就不貼了,本質就是 ThreadLocal 緩存了一個sessionFactorty // 爲key的, sessionHolder 爲value的map, 這樣每一個線程都有本身的sqlsession,執行時沒有線程同步問題 // 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"); } // 若是沒有緩存就open一個,而後 regist,即緩存起來 session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; } }
public SqlSession openSession(ExecutorType execType) { return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; DefaultSqlSession var8; try { Environment e = this.configuration.getEnvironment(); // 從 Environment 獲取 TransactionFactory // transactionFactory.newTransaction 開啓新事務 // TransactionFactory 接口有3個實現類 // 1. JdbcTransactionFactory // 2. SpringManagedTransactionFactory // 3. ManagedTransactionFactory // 當獨立使用時使用的是1,當與spring結合時使用的是3(後面說明這個) TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(e); tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit); Executor executor = this.configuration.newExecutor(tx, execType); var8 = new DefaultSqlSession(this.configuration, executor, autoCommit); } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); } return var8; }
public class SpringManagedTransactionFactory implements TransactionFactory { public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { return new SpringManagedTransaction(dataSource); } } // 簡化省略的代碼 public class SpringManagedTransaction implements Transaction { public Connection getConnection() throws SQLException { if(this.connection == null) { this.openConnection(); } return this.connection; } private void openConnection() throws SQLException { // DataSourceUtils.getConnection 是獲取當前線程的conn,也是ThreadLocal方式 // key爲ds,value就是conn // 若是事務框架已經開啓事務,那麼當前線程已經換成conn返回便可,沒有的話經過ds獲取一個再緩存 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"); } } }
到這已經明瞭,最終 SpringManagedTransaction 控制着 openConnection 大權,而它索要過來的conn是來自「官方」(spring)事務管理的conn。
這時,無論聲明式事務和編程式事務只要遵照spring事務管理的都能起做用。
上面遺留一個問題:SpringManagedTransactionFactory 是什麼時候被裝配進 Evn中的?
這個要回到 SqlSessionFactoryBean 中
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { // ... 解析 XML配置,如cofnig mybatis-config.xml 及 mapperLocations 等 // 代碼 省略 // 就是這裏將 SpringManagedTransactionFactory 配置到 Env 中 if(this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // ... }
回到上面 1 的最後 SqlSessionUtils.closeSqlSession(),是否是真的將sqlSession關閉?sqlSession的關閉會把事務關閉或者鏈接關閉麼?
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { Assert.notNull(session, "No SqlSession specified"); Assert.notNull(sessionFactory, "No SqlSessionFactory specified"); SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory); // 只有 holder 丟失或者 session 不一致纔會真實 session.close // 其餘狀況只是 holder.released() 將引用數減一 if(holder != null && holder.getSqlSession() == session) { if(LOGGER.isDebugEnabled()) { LOGGER.debug("Releasing transactional SqlSession [" + session + "]"); } holder.released(); } else { if(LOGGER.isDebugEnabled()) { LOGGER.debug("Closing non transactional SqlSession [" + session + "]"); } session.close(); } }
session.close() , DefaultSqlSession 的源碼:
public void close() { try { this.executor.close(this.isCommitOrRollbackRequired(false)); this.dirty = false; } finally { ErrorContext.instance().reset(); } } // this.executor.close 代碼: public void close(boolean forceRollback) { try { try { this.rollback(forceRollback); } finally { if(this.transaction != null) { // 調用了 tx 的close this.transaction.close(); } } } catch (SQLException var11) { log.warn("Unexpected exception on closing transaction. Cause: " + var11); } finally { this.transaction = null; this.deferredLoads = null; this.localCache = null; this.localOutputParameterCache = null; this.closed = true; } } // 這裏的tx 是 SpringManagedTransaction, 上面已經分析 public void close() throws SQLException { DataSourceUtils.releaseConnection(this.connection, this.dataSource); } // 中間代碼省略,最終代碼 public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException { // 當 holder沒有丟失,conn 仍是一致時,並不會真正的release if(con != null) { if(dataSource != null) { ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource); if(conHolder != null && connectionEquals(conHolder, con)) { conHolder.released(); return; } } logger.debug("Returning JDBC Connection to DataSource"); doCloseConnection(con, dataSource); } }
可見,mybatis的close在通常狀況下並不會真正去調用 conn.close(), 而是拖給 SpringManagedTransaction 去處理判斷是否真實close,仍是holder.released()。