本文将结合示例代码、阅读源码,去深入了解mybatis与spring boot集成的底层原理。
示例代码
示例展示最小化配置。
依赖
只记录相关内容:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
application.yml配置
只记录相关内容:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_source_analysis
username: root
password: 123456
druid:
filters: stat
maxActive: 5
initialSize: 5
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 5
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
mapper xml配置文件
与之前一样,不记录。
MybatisConfig配置类
@MapperScan(basePackages = {"org.net5ijy.mybatis.test.mapper"})
@Configuration
public class MybatisConfig {
}
Mapper接口
与之前一样,不记录。
接口层编写
只记录相关内容:
@RestController
@RequestMapping("/blog")
public class BlogController {
private BlogMapper blogMapper;
public BlogController(BlogMapper blogMapper) {
this.blogMapper = blogMapper;
}
}
启动类
@SpringBootApplication
public class MybatisSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisSpringBootApplication.class, args);
}
}
核心原理
我们依赖的mybatis-spring-boot-starter包,而这个包没有实际代码和配置文件,只是引入了mybatis、mybatis-spring、和mybatis-spring-boot-autoconfigure等包。
自动装配的实现在mybatis-spring-boot-autoconfigure包里面。
我们知道spring boot的自动装配原理是在自动装配包的META-INF下创建spring.factories文件,在文件里面配置EnableAutoConfiguration的配置类全名即可实现自动装配,我们看一下mybatis-spring-boot-autoconfigure包的spring.factories文件:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
核心内容就在MybatisAutoConfiguration这个类。
所以这个类会帮助我们创建SqlSessionFactory和SqlSessionTemplate,而DataSource是由DataSourceAutoConfiguration类来做的(这个暂不讨论)。
所以应该从MybatisAutoConfiguration为入口开始分析。
其实基本上所有的第三方库的集成都是这样的原理:
- 一个starter库
- 一个autoconfigure库
- 一个spring.factories文件,其实这个才是最重要的。至于是一个包还是两个包,都问题不大,可以实现就行
- 一个AutoConfiguration类
- 一个Properties类
至于spring.factories文件被加载的原理,这是spring boot的内容,本文不做扩展分析。
源码分析
MybatisAutoConfiguration类
这个类负责创建SqlSessionFactory和SqlSessionTemplate对象。
看一下这个类的成员和构造方法:
public class MybatisAutoConfiguration implements InitializingBean {
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) {
}
创建SqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
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);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors())
.map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
return factory.getObject();
}
private void applyConfiguration(SqlSessionFactoryBean factory) {
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
}
创建SqlSessionTemplate
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
代码比较简单,就是创建SqlSessionTemplate对象。
MybatisProperties配置类
MybatisProperties配置类封装了用户的所有mybatis配置,这个类读取mybatis.开始的配置参数,封装到自己的属性中。
类定义:
@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties {
}
支持的配置:


从图中可以看到,这类本身支持的配置并不是很多:
mapperLocations - mapper xml配置文件位置
checkConfigLocation - 是否对主配置文件进行检查,默认false不检查
configLocation - 主配置文件位置
configurationProperties - 主配置扩展属性
defaultScriptingLanguageDriver - 默认的脚本语言模板类
executorType - SqlSessionTemplate的运行模式
typeAliases* - 类型别名
typeHandlersPackage - 类型处理器
其余的以mybatis.configuration.开始的配置封装在MybatisProperties的Configuration对象中,这个对象封装mybatis的核心配置,属性较多。
以mybatis.scripting-language-driver.开始的配置是脚本语言模板类的属性,本文不介绍。
@MapperScan注解原理(略)
在 mybatis源码分析_集成spring源码分析 文中有详细分析,此处略。
插件加载原理
三种方式
- 使用@Component标注Interceptor实现类
- 使用ConfigurationCustomizer的customize方法向Configuration addInterceptor
- 编写mybatis主配置文件,使用plugins标签注册
@Component标注Interceptor实现类
在之前分析MybatisAutoConfiguration时看过构造方法,其中让spring注入了一个ObjectProvider<Interceptor[]>对象,这个对象里面是容器里面所有的Interceptor接口的实例对象。
ObjectProvider接口是spring 4.3提供的新特性,是ObjectFactory接口的扩展,专门为注入点设计的,可以让注入变得更加宽松和更具有可选项。
如果待注入参数的bean为空或有多个时,ObjectProvider就会起作用:
- 如果注入实例为空时,使用ObjectProvider则避免了强依赖导致的依赖对象不存在异常
- 如果有多个实例,ObjectProvider的方法会根据bean实现的Ordered接口或@Order注解指定的先后顺序获取一个Bean。从而了提供了一个更加宽松的依赖注入方式
所以就可以理解这种方法了。
所以不只是使用@Component标注Interceptor实现类,在Configuration配置类中使用@Bean创建bean方式也是可行的。
@Bean
public PrintSqlInterceptor printSqlInterceptor() {
return new PrintSqlInterceptor();
}
ConfigurationCustomizer的customize方法
MybatisAutoConfiguration的构造方法还注入了ObjectProvider<List<ConfigurationCustomizer>>对象,其原理和前文一样,我们需要了解的是ConfigurationCustomizer接口:
@FunctionalInterface
public interface ConfigurationCustomizer {
void customize(Configuration configuration);
}
这是一个函数接口,customize方法接收一个mybatis的Configuration对象作为参数,可以使用Configuration的addInterceptor(Interceptor)方法注册插件。
其实不止是插件,其他操作Configuration对象的需求也可以使用这个接口来实现。