该系列文档是本人在学习 Mybatis 的源码过程当中总结下来的,可能对读者不太友好,请结合个人源码注释(Mybatis源码分析 GitHub 地址、Mybatis-Spring 源码分析 GitHub 地址、Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读html
MyBatis 版本:3.5.2java
MyBatis-Spring 版本:2.0.3git
MyBatis-Spring-Boot-Starter 版本:2.1.4github
在《MyBatis-Spring源码分析》文档中对 Spring 集成 MyBatis 的方案进行了分析,MyBatis-Spring
让你可以在 Spring 项目中方便地使用 MyBatis,随着 Spring Boot 框架受到业界的普遍关注,有愈来愈多企业将它使用到正式的生产环境,它支持整合其余组件,让你可以在 Spring Boot 项目中更加方便地使用其余组件spring
固然,MyBatis 也提供了整合到 Spring Boot 的方案 Spring-Boot-Starter
,可以让你快速的在 Spring Boot 上面使用 MyBatis,那么咱们来看看这个 Spring-Boot-Starter 子项目 是如何将 MyBatis 集成到 Spring 中的sql
在开始读这篇文档以前,须要对 Spring 有必定的了解,其中Spring-Boot-Starter
基于 MyBatis-Spring
来实现的,因此能够先查看个人另外一篇《MyBatis-Spring源码分析》文档来了解 MyBatis-Spring
,本文能够结合个人源码注释(Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读数据库
MyBatis 的 Spring-Boot-Starter
子项目咱们主要看到两个模块apache
主要涉及到的几个类:json
org.mybatis.spring.boot.autoconfigure.MybatisProperties
:MyBatis 的配置类,注入 Spring Boot 的配置文件中 MyBatis 的相关配置org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
:实现 InitializingBean 接口,MyBatis 自动配置类,用于初始化 MyBatis,核心类大体逻辑以下:数组
经过 MybatisAutoConfiguration
这个自动配置类,再加上 MybatisProperties
的配置信息,生成 SqlSessionFactory 和 SqlSessionTemplate 类,完成初始化,经过 @MapperScan 注解指定 Mapper 接口
mybatis: type-aliases-package: tk.mybatis.simple.model mapper-locations: classpath:mapper/*.xml config-location: classpath:mybatis-config.xml
在application.yml
中添加上面三个MyBatis的相关配置便可,而后在启动类上面添加@MapperScan
注解指定 Mapper 接口所在包路径便可
注意:你还须要定义一个 DataSource 数据源,可选 Druid
、HikariCP
等数据库链接池,这里就不讲述如何使用了
org.mybatis.spring.boot.autoconfigure.MybatisProperties
:MyBatis 的配置类,经过 Spring Boot 中的 @ConfigurationProperties
注解,注入 MyBatis 的相关配置,代码以下:
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX) public class MybatisProperties { public static final String MYBATIS_PREFIX = "mybatis"; private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); /** * Location of MyBatis xml config file. * mybatis-config.xml 配置文件的路径 */ private String configLocation; /** * Locations of MyBatis mapper files. * XML 映射文件的路径 */ private String[] mapperLocations; /** * Packages to search type aliases. (Package delimiters are ",; \t\n") * 须要设置别名的包路径 */ private String typeAliasesPackage; /** * The super class for filtering type alias. If this not specifies, the MyBatis deal as type alias all classes that * searched from typeAliasesPackage. */ private Class<?> typeAliasesSuperType; /** * Packages to search for type handlers. (Package delimiters are ",; \t\n") */ private String typeHandlersPackage; /** * Indicates whether perform presence check of the MyBatis xml config file. */ private boolean checkConfigLocation = false; /** * Execution mode for {@link org.mybatis.spring.SqlSessionTemplate}. */ private ExecutorType executorType; /** * The default scripting language driver class. (Available when use together with mybatis-spring 2.0.2+) */ private Class<? extends LanguageDriver> defaultScriptingLanguageDriver; /** * Externalized properties for MyBatis configuration. */ private Properties configurationProperties; /** * A Configuration object for customize default settings. If {@link #configLocation} is specified, this property is * not used. */ @NestedConfigurationProperty private Configuration configuration; /** * 获取 XML 映射文件路径下的资源对象 * * @return Resource 资源数组 */ public Resource[] resolveMapperLocations() { return Stream.of(Optional.ofNullable(this.mapperLocations).orElse(new String[0])) .flatMap(location -> Stream.of(getResources(location))).toArray(Resource[]::new); } /** * 获取某个路径下的资源 * * @param location 路径 * @return Resource 资源数组 */ private Resource[] getResources(String location) { try { return resourceResolver.getResources(location); } catch (IOException e) { return new Resource[0]; } } }
configLocation
:mybatis-config.xml 配置文件的路径mapperLocations
:XML 映射文件的路径typeAliasesPackage
:须要设置别名的包路径,多个以, ; \t\n
分隔typeAliasesSuperType
:须要设置别名的父 Class 类型typeHandlersPackage
:类型处理器的包路径checkConfigLocation
:检查 mybatis-config.xml 配置文件是否存在executorType
:Executor 执行器类型,默认 SIMPLEdefaultScriptingLanguageDriver
:设置默认的 LanguageDriver 语言驱动类,默认为 XMLLanguageDriver其中定义了前缀为mybatis
,说明你能够在 Spring Boot 项目中的 application.yml 配置文件中,以该前缀定义 MyBatis 的相关属性
咱们一般添加前面三个配置就能够了
这里注意到仅添加了 @ConfigurationProperties 注解,在做为 Spring Bean 注入到 Spring 容器中时,会将相关配置注入到属性中,可是这个注解不会将该类做为 Spring Bean 进行注入,须要结合 @Configuration 注解或者其余注解一块儿使用
org.mybatis.spring.boot.autoconfigure.SpringBootVFS
:MyBatis 须要使用到的虚拟文件系统,用于替代 MyBatis 的 org.apache.ibatis.io.DefaultVFS
默认类
使用 Spring Boot 提供的 PathMatchingResourcePatternResolver 解析器,获取到指定路径下的 Resource 资源,代码以下:
public class SpringBootVFS extends VFS { private final ResourcePatternResolver resourceResolver; public SpringBootVFS() { this.resourceResolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader()); } @Override public boolean isValid() { return true; } @Override protected List<String> list(URL url, String path) throws IOException { String urlString = url.toString(); String baseUrlString = urlString.endsWith("/") ? urlString : urlString.concat("/"); Resource[] resources = resourceResolver.getResources(baseUrlString + "**/*.class"); return Stream.of(resources).map(resource -> preserveSubpackageName(baseUrlString, resource, path)) .collect(Collectors.toList()); } private static String preserveSubpackageName(final String baseUrlString, final Resource resource, final String rootPath) { try { return rootPath + (rootPath.endsWith("/") ? "" : "/") + resource.getURL().toString().substring(baseUrlString.length()); } catch (IOException e) { throw new UncheckedIOException(e); } } }
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
:实现 InitializingBean 接口,MyBatis 自动配置类,用于初始化 MyBatis,核心类
@org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnSingleCandidate(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) public class MybatisAutoConfiguration implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class); /** * MyBatis 配置信息 */ private final MybatisProperties properties; private final Interceptor[] interceptors; private final TypeHandler[] typeHandlers; private final LanguageDriver[] languageDrivers; private final ResourceLoader resourceLoader; private final DatabaseIdProvider databaseIdProvider; private final List<ConfigurationCustomizer> configurationCustomizers; public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) { this.properties = properties; this.interceptors = interceptorsProvider.getIfAvailable(); this.typeHandlers = typeHandlersProvider.getIfAvailable(); this.languageDrivers = languageDriversProvider.getIfAvailable(); this.resourceLoader = resourceLoader; this.databaseIdProvider = databaseIdProvider.getIfAvailable(); this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); } }
咱们主要来看到类上面定义的几个注解:
@Configuration
:能够看成一个 Spring Bean 注入到 Spring 上下文中
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
保证存在 value 中全部的 Class 对象,以确保能够建立它们的实例对象,这里就保证 SqlSessionFactory 和 SqlSessionFactoryBean 都可以被建立
@ConditionalOnSingleCandidate(DataSource.class)
保证存在 value 类型对应的 Bean,这里确保已经存在一个 DataSource 数据源对象
@EnableConfigurationProperties(MybatisProperties.class)
注入 value 中全部的类型的 Bean,这里会让 MybatisProperties 做为 Spring Bean 注入到 Spring 上下文中
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
在加载 value 中的全部类以后注入当前 Bean,会先注入 DataSourceAutoConfiguration 和 MybatisLanguageDriverAutoConfiguration 两个类(感兴趣的能够去看看,我没搞懂这两个类😈 😈 😈 )
咱们能够看到是经过构造函数注入 MybatisAutoConfiguration ,其中会注入 MybatisProperties
和 ResourceLoader
两个 Bean,其余的对象都是经过 ObjectProvider
进行注入的(经过它的getIfAvailable()
方法,若是存在对应的实例对象则设置)
afterPropertiesSet()
方法,实现的 InitializingBean 接口的方法,在 Spring 容器初始化该 Bean 时会调用该方法
用于校验 mybatis-config 配置文件是否存在,方法以下:
@Override public void afterPropertiesSet() { checkConfigFileExists(); } private void checkConfigFileExists() { if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); } }
若是配置了 MybatisProperties 中配置checkConfigLocation
须要检查 mybatis-config.xml 配置文件,那么就会检查该文件是否有对应的 Resource 资源,文件不存在则会抛出异常
sqlSessionFactory(DataSource dataSource)
方法,注入一个 SqlSessionFactory 类型的 Spring Bean 到 Spring 上下文,方法以下:
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { // <1> 建立一个 SqlSessionFactoryBean 对象,在 mybatis-spring 子项目中 SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); // <2> 设置数据源 factory.setDataSource(dataSource); // <3> 设置虚拟文件系统为 SpringBootVFS 对象 factory.setVfs(SpringBootVFS.class); /* * <4> 接下来设置一些属性 */ // 若是配置了 mybatis-config.xml 配置文件 if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } // <5> 应用 Configuration 对象 applyConfiguration(factory); if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperties()); } if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } // 若是配置了须要设置别名包路径 if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (this.properties.getTypeAliasesSuperType() != null) { factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.typeHandlers)) { factory.setTypeHandlers(this.typeHandlers); } // 若是配置了 XML 映射文件路径 if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } // 获取 SqlSessionFactoryBean 对象中属性的名称 Set<String> factoryPropertyNames = Stream .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()) .map(PropertyDescriptor::getName) .collect(Collectors.toSet()); Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); // 若是包含了 scriptingLanguageDrivers 属性,而且存在语言驱动类 if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) { // Need to mybatis-spring 2.0.2+ // 设置语言驱动类 factory.setScriptingLanguageDrivers(this.languageDrivers); if (defaultLanguageDriver == null && this.languageDrivers.length == 1) { defaultLanguageDriver = this.languageDrivers[0].getClass(); } } // 若是包含了 defaultScriptingLanguageDriver 属性 if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { // Need to mybatis-spring 2.0.2+ // 设置默认的语言驱动类 factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); } // <6> 这里会初始化(经过 afterPropertiesSet() 方法),返回一个 DefaultSqlSessionFactory 对象 return factory.getObject(); }
@ConditionalOnMissingBean
注解:不存在同类型则注入当前 Bean(DefaultSqlSessionFactory),存在则不注入
建立一个 SqlSessionFactoryBean
对象,在《MyBatis-Spring源码分析》已经讲到过
设置数据源 DataSource
设置 VFS 虚拟文件系统为 SpringBootVFS
对象
接下来设置一些属性,例如 configLocation、typeAliasesPackage、mapperLocation等属性
应用 Configuration 对象
private void applyConfiguration(SqlSessionFactoryBean factory) { Configuration configuration = this.properties.getConfiguration(); if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { // 若是没有自定义 Configuration 对象,也没有定义 configLocation 配置文件,则直接建立 configuration = new Configuration(); } /* * 若是 Configuration 不为 null,而且 ConfigurationCustomizer 处理器不为空 * 则对该 Configuration 对象进行自定义处理 */ if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { for (ConfigurationCustomizer customizer : this.configurationCustomizers) { customizer.customize(configuration); } } factory.setConfiguration(configuration); }
若是你在 MapperProperties 中定义了 Configuration 对象,或者你没有配置 configLocation(会建立一个 Configuration)
那么会经过 ConfigurationCustomizer 处理器(须要本身去实现该接口),对该 Configuration 对象进行自定义处理
调用SqlSessionFactoryBean
的 getObject() 方法,返回一个 DefaultSqlSessionFactory 对象,这里会调用其 afterPropertiesSet() 方法,完成 MyBatis 的初始化
sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
方法,注入一个 SqlSessionTemplate 类型的 Spring Bean 到 Spring 上下文,方法以下:
@Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { // 获取执行器类型,默认 SIMPLE ExecutorType executorType = this.properties.getExecutorType(); if (executorType != null) { return new SqlSessionTemplate(sqlSessionFactory, executorType); } else { return new SqlSessionTemplate(sqlSessionFactory); } }
@ConditionalOnMissingBean
注解:不存在同类型则注入当前 Bean(SqlSessionTemplate),存在则不注入
根据 SqlSessionFactory 建立一个 SqlSessionTemplate 对象
这里怎么才能被
MapperFactoryBean
注入 sqlSessionTemplate 属性为当前 SqlSessionTemplate 对象呢?在 org.mybatis.spring.mapper.ClassPathMapperScanner 的 processBeanDefinitions 方法中你会发现,若是没有配置 sqlSession 相关配置,则 explicitFactoryUsed 为 false,那么就会设置该 MapperFactoryBean 的 AutowireMode 为
AUTOWIRE_BY_TYPE
,也就是说经过属性类型注入值,经过 set 方法来赋值,就会找到这个 SqlSessionTemplate 对象了
MybatisAutoConfiguration 的一个内部静态类,实现了 InitializingBean 接口
用于注入一个 AutoConfiguredMapperScannerRegistrar
静态类,代码以下:
/** * If mapper registering configuration or mapper scanning configuration not present, * this configuration allow to scan mappers based on the same component-scanning path as Spring Boot itself. * */ @org.springframework.context.annotation.Configuration @Import(AutoConfiguredMapperScannerRegistrar.class) @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { @Override public void afterPropertiesSet() { logger.debug( "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); } }
若是 MapperFactoryBean 和 MapperScannerConfigurer 类型的 Bean 都不存在则注入该 Bean,则经过 @Import(AutoConfiguredMapperScannerRegistrar.class)
导入的 AutoConfiguredMapperScannerRegistrar
类,会去扫描 Spring Boot 项目的基础包路径,带有 @Mapper
注解的接口会被当成 Mapper接口 进行解析
MybatisAutoConfiguration 的一个内部静态类,实现了 BeanFactoryAware 和 ImportBeanDefinitionRegistrar 接口
用于往 BeanDefinitionRegistry 注册表添加一个 MapperScannerConfigurer
类型的 BeanDefinition 对象,该对象在《MyBatis-Spring源码分析》已经讲过,代码以下:
/** * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box, * similar to using Spring Data JPA repositories. */ public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar { private BeanFactory beanFactory; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!AutoConfigurationPackages.has(this.beanFactory)) { logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled."); return; } logger.debug("Searching for mappers annotated with @Mapper"); // <1> 获取到 Spring Boot 的基础包路径 List<String> packages = AutoConfigurationPackages.get(this.beanFactory); if (logger.isDebugEnabled()) { packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg)); } // <2> 生成一个 BeanDefinition 构建器,用于构建 MapperScannerConfigurer 的 BeanDefinition 对象 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders", true); // <3> 设置 @Mapper 注解的接口才会被当成 Mapper 接口 builder.addPropertyValue("annotationClass", Mapper.class); builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); // 获取 MapperScannerConfigurer 的属性名称 Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName) .collect(Collectors.toSet()); if (propertyNames.contains("lazyInitialization")) { // Need to mybatis-spring 2.0.2+ builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"); } if (propertyNames.contains("defaultScope")) { // Need to mybatis-spring 2.0.6+ builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}"); } // <4> 添加 一个 MapperScannerConfigurer 的 BeanDefinition 对象,也就是注入一个 MapperScannerConfigurer 对象到容器中 registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } }
MapperScannerConfigurer
的 BeanDefinition 对象,也就是注入一个 MapperScannerConfigurer 对象到容器中这个类和@MapperScan注解同样的做用,若是你没有经过下面三种配置方式扫描 Mapper 接口的包路径
配置 MapperScannerConfigurer
扫描器类型的 Spring Bean
@MapperScan
注解
<mybatis:scan />
标签)
那么这里就会经过AutoConfiguredMapperScannerRegistrar
添加一个 MapperScannerConfigurer
扫描器对象,去扫描 Spring Boot 项目设置的基础包路径,若是配置了@Mapper
注解,则会当成Mapper接口进行解析,和@MapperScan
注解的做用同样
spring.factories
文件位于resources/META-INF
下面,内容以下:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguion
上面讲到的 MybatisAutoConfiguration
自定配置类做为初始化 MyBatis 的入口,须要被 Spring 容器管理,可是他人经过引入该组件后,这些类不必定可以被 Spring 扫描到,因此须要经过 spring.factories
文件来定义 org.springframework.boot.autoconfigure.EnableAutoConfiguration
的类名值,那么这些类名对应的类就会必定会被 Spring 管理了(SPI 机制)
additional-spring-configuration-metadata.json
文件,该文件定义的内容是在 Spring Boot 项目中的 application.yml
配置文件中 mybatis 相关配置的默认值以及提示等信息经过 spring.factories
文件,在你引入 MyBatis 的 mybatis-spring-boot-starter
依赖后,MybatisAutoConfiguion
自定配置类将会做为 Spring Bean 注入到 Spring 的上下文中,从这个类中会初始化 SqlSessionFactory 和 SqlSessionTemplate 两个对象,完成初始化,另外能够经过 @MapperScan
注解解析对应的 Mapper 接口
实际上就是在 MyBatis-Spring 的子项目上增长对 Spring Boot 配置文件以及注解的支持,不用在配置文件中定义相应的 Bean 了,相对来讲比较简单
到这里已经对 MyBatis 相关内容所有分析玩了,相信你们对 MyBatis 有了一个更加深刻的了解,感谢你们的阅读!!!😄😄😄
参考文章:芋道源码《精尽 MyBatis 源码分析》