我在使用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总体设计也很是巧妙,扩展性很是强。