本文主要关注与 SpringBoot 集成时的初始化过程。
整体架构如下图:
SpringBoot 自动组装 MyBatis 的属性(前缀为 “mybatis”)配置成 MybatisProperties
对象。
SpringBoot 的自动配置机制会解析配置类 MybatisAutoConfiguration
,其内部方法定义了两个 Bean 。
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {...} @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {...}
在 sqlSessionFactory(dataSource)
方法里利用 MybatisProperties
初始化 SqlSessionFactoryBean
,通过 SqlSessionFactoryBean.buildSqlSessionFactory()
来创建 Configuration
,该方法的主要逻辑是创建并配置 Configuration
,然后用 Configuration
创建 SqlSessionFactory
。
再用 SqlSessionFactory
创建 SqlSessionTemplate
。
SpringBoot 会解析处理配置类上的 @MapperScan("package.to.scan")
注解,调用该注解上的导入类 MapperScannerRegistrar
; MapperScannerRegistrar.registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
方法创建一个 ClassPathMapperScanner
,利用 @MapperScan
上的信息初始化这个扫描器,以限定扫描的接口范围,对于扫描到的每个 bean 定义,设置其 beanClass
为 MapperFactoryBean
,如果有指定 sqlSessionFactory/sqlSessionTemplate
bean 的引用则直接使用,否则采用按类型自动注入。
ClassPathMapperScanner
默认扫描所有的接口并进行注册。
SqlSessionTemplate 是 线程安全、Spring 管理的 SqlSession ,可以注入到其他 Spring 管理的 bean 里去使用。与 Spring 事务管理机制一起工作以保证实际使用的 SqlSession 是与当前的 Spring 事务关联的。另外,它管理着会话的生命周期,包括关闭、提交或回滚会话,基于 Spring 的事务配置。
该模板默认使用 MyBatisExceptionTranslator
把 MyBatis PersistenceExceptions
转换为不受检查的异常 DataAccessExceptions
。
因为 SqlSessionTemplate
是线程安全的,一个实例可以被所有的 DAOs 共享,这样也可以节省点内存。
public class SqlSessionTemplate implements SqlSession, DisposableBean { // 用于创建 SqlSession private final SqlSessionFactory sqlSessionFactory; // 执行器的类型,默认取 sqlSessionFactory 里的 private final ExecutorType executorType; // 拦截对 SqlSession 接口里声明的方法的调用,以便与 Spring 事务管理集成 private final SqlSession sqlSessionProxy; // 异常转换器 private final PersistenceExceptionTranslator exceptionTranslator; }
下面是创建 Configuration
对象的过程:
// SqlSessionFactoryBean protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } if (this.transactionFactory == null) { // 设置事务工厂为 SpringManagedTransactionFactory,以便与 Spring 管理的事务进行协作 this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // 适用于 Mapper 的 XML 文件集中存放的场景 if (!isEmpty(this.mapperLocations)) { // 如果指定了 Mapper 存放的路径则解析 for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } } } return this.sqlSessionFactoryBuilder.build(configuration); } // SqlSessionFactoryBuilder public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
Mapper 的扫描一是可以通过属性 mybatis.mapperLocations
来指定其存放路径,也可以通过 @MapperScan("package.to.scan")
注解来配置。
该注解扫描指定包下的所有接口、并为其生成代理注册到 Spring 的容器里。
MapperScannerRegistrar
从 @MapperScan
注解获取扫描配置,用以初始化 ClassPathMapperScanner
并完成最终的扫描工作。
public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; } private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 // 设置bean定义的类为 MapperFactoryBean definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { // 如果没有显式设置 sqlSessionTemplate 或 sqlSessionFactory 则按类型注入 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }
上述代码最重要的是 设置 bean 定义的类为 MapperFactoryBean
。
Mapper 的 Bean 定义被扫描后会注册到 Spring 容器里,再由 Spring 实例化时调用 MapperFactoryBean.getObject()
方法得到该类型的 Bean 实例,注册到容器里,供应用使用。
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; private boolean externalSqlSession; public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSession = sqlSessionTemplate; this.externalSqlSession = true; } public SqlSession getSqlSession() { return this.sqlSession; } // 省略其他 } public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { // 该方法在父类的 afterPropertiesSet 里调用 // 用于把接口类型注册到 Configuration 里。供后面创建代理时使用 protected void checkDaoConfig() { super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { 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(); } } } public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } // 省略其他 }
SqlSessionDaoSupport
的主要作用是把 sqlSession
设置为 SqlSessionTemplate
, DaoSupport
提供了属性设置后回调的初始化钩子,触发调用 MapperFactoryBean.checkDaoConfig
方法。
下面的代理的创建调用链:
// SqlSessionTemplate public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); } // Configuration public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } // MapperRegistry public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } // MapperProxyFactory protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
最终创建了一个 JDK 的动态代理,代理的方法处理器为 MapperProxy
,其持有的 SqlSession
仍然是 SqlSessionTemplate
的实例。
欢迎关注我的微信公众号: coderbee笔记 ,可以更及时回复你的讨论。