我在使用mybatis-spring過程當中一直有一個疑問,在Mybatis 源碼(一)總攬中我提到過,SqlSession
和Mapper對象的聲明週期是方法級別的,也就是每一個請求的SqlSession
和Mapper對象是不同的,是一個非單例的Bean。可是與Spring集成後,爲何咱們能夠直接注入Mapper對象,若是經過直接注入的話Mapper對象卻成了單例的了?java
咱們帶着疑問來看下Mybatis-Spring是如何實現的。git
咱們是經過SqlSessionFactoryBean
來完成Mybatis與Spring集成的,類圖以下:github
經過類圖咱們發現SqlSessionFactoryBean
實現了FactoryBean
接口,那麼在Spring實例化Bean的時候會調用FactoryBean
的getObject()
方法。因此Mybatis與Spring的集成的入口就是org.mybatis.spring.SqlSessionFactoryBean#getObject()
方法,源碼以下:spring
public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }
經過跟源碼發現,在afterPropertiesSet();
方法中完成了sqlSessionFactory
的初始化。sql
和Mybatis 源碼(二)Mybatis 初始化中介紹的同樣,仍是經過XMLConfigBuilder
、XMLMapperBuilder
和XMLStatementBuilder
三個建造者來完成了對Mybatis XML文件的解析。數據庫
Mybatis和Spring集成後,有三種方式將Mapper的實例裝載到Spring容器,以下:設計模式
<mybatis:scan/>
元素@MapperScan
註解MapperScannerConfigurer
在這裏咱們介紹一下MapperScannerConfigurer
。安全
經過類圖咱們發現,MapperScannerConfigurer
實現了BeanDefinitionRegistryPostProcessor
,那麼會執行BeanDefinitionRegistryPostProcessor
的postProcessBeanDefinitionRegistry
方法來完成Bean的裝載。session
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
咱們能夠看到經過包掃描,將會掃描出全部的Mapper類,而後註冊Bean定義到Spring容器。mybatis
可是Mapper是一個接口類,是不能直接進行實例化的,因此在ClassPathMapperScanner
中,它將全部Mapper對象的BeanDefinition
給改了,將全部Mapper的接口對象指向MapperFactoryBean
工廠Bean,因此在Spring中Mybatis全部的Mapper接口對應的類是MapperFactoryBean
,源碼以下:
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); for (BeanDefinitionHolder holder : beanDefinitions) { GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); definition.setBeanClass(MapperFactoryBean.class); definition.getPropertyValues().add("addToConfig", this.addToConfig); ... // 設置按類型注入屬性,這裏主要是注入sqlSessionFactory和sqlSessionTemplate definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } return beanDefinitions; }
從源碼咱們看出ClassPathMapperScanner
主要以下的BeanDefinition
:
MapperFactoryBean
addToConfig
、sqlSessionFactory
、sqlSessionFactory
AbstractBeanDefinition.AUTOWIRE_BY_TYPE
經過上述修改使得Mapper接口能夠實例化成對象並放到Spring容器中。
從類圖咱們能夠看出它是一個FactoryBean
,因此實例化的時候回去調用其getObject()
方法完成Bean的裝載,源碼以下:
@Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }
這裏值得說一下的是,getSqlSession()
獲取到的是SqlSessionTemplate
對象,在Mapper是單例的狀況下,如何保證每次訪問數據庫的Sqlsession
是不同的,就是在SqlSessionTemplate
中實現的。
MapperFactoryBean
它還實現了InitializingBean
接口,利用InitializingBean
的特性,它會將Mapper接口放到Mybatis中的Configuration
對象中,源碼以下:
@Override public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { // Let abstract subclasses check their configuration. checkDaoConfig(); ... } protected void checkDaoConfig() { super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { // 將Mapper放到Mybatis的Configuration對象中 configuration.addMapper(this.mapperInterface); } catch (Exception e) { logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e); throw new IllegalArgumentException(e); } finally { ErrorContext.instance().reset(); } } }
Mapper接口注入到Spring容器能夠等價與以下配置,這個看起來更好理解:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
MapperProxy
,而且在容器中它是單例的。Sqlsession
不是單例的就行。爲了實現這一點,MapperProxy
的SqlSession
不是直接使用DefaultSqlSession
,而是使用了SqlSessionTemplate
。SqlSessionTemplate
使用了動態代理模式+靜態代理模式,對SqlSession進行加強,每次請求數據庫使用新的SqlSession
放到了加強器SqlSessionInterceptor
裏面來實現。
public class SqlSessionTemplate implements SqlSession, DisposableBean { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; // 使用動態代理模式,對SqlSession進行加強 this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionInterceptor()); } /** * {@inheritDoc} */ @Override public <T> T selectOne(String statement) { return this.sqlSessionProxy.selectOne(statement); } }
這個纔是Mybatis-Spring實現原理的核心之一,在每次請求數據庫的過程當中它會新建立一個SqlSession
,源碼以下:
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 每次獲取新的SqlSession SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() // 事務提交 sqlSession.commit(true); } return result; } catch (Throwable t) { // 異常處理 Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 // 資源釋放 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { // 資源釋放 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
在這個加強器中它實現了每次請求新建立Sqlsession
,而且還作了統一的資源釋放,事務處理等,這使得咱們在和Spring集成後,不用關心資源釋放等操做,將工做重心放到業務上。
Mybatis-Spring的實現有兩個核心點:
MapperFactoryBean
巧妙的將Mapper接口對應的代理對象MapperProxy
裝載到了Spring容器中。SqlSessionTemplate
,使用靜態代理+動態代理模式,巧妙的實現了每次訪問數據庫都是用新的Sqlsession
對象。https://github.com/xiaolyuh/mybatis
在這裏Mybatis源碼系列就寫完了,與Spring源碼相比,我很是建議你們去看下Mybatis源碼,緣由有如下幾點:
- Mybatis源碼很是工整,包結構、代碼結構都很值得咱們學習。
- Mybatis中使用不少設計模式,大部分設計模式均可以在這裏找到其身影,能夠當作是設計模式的最佳實踐。
- Mybatis總體設計也很是巧妙,擴展性很是強。