mybatis源码分析系列:
- mybatis源码看这一遍就够了(1)| 前言
- mybatis源码看这一遍就够了(2)| getMapper
- mybatis源码看这一遍就够了(3)| Configuration及解析配置文件
- mybatis源码看这一遍就够了(4)| SqlSession.select调用分析
- mybatis源码看这一遍就够了(5)| 与springboot整合
通过前面几章对mybatis的源码分析,相信大家对mybatis的流程原理也有了一定的认识,下面将对mybatis与springboot整合的源码进行分析。
在分析整合之前,我们先来个springboot+myBatis的demo,以便我们进一步分析和加深印象
一、demo
1.pom引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>
2.application.yml配置信息:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapper-locations: classpath*:mybatis/*Mapper.xml
3.mapper文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cwh.dao.UserMapper">
<select id="select" resultType="com.cwh.entity.User">
select * from user
</select>
</mapper>
4.mapper:
@Mapper
public interface UserMapper {
List<User> select();
}
5.springboot启动类:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
UserMapper userMapper = run.getBean(UserMapper.class);
List<User> users = userMapper.select();
System.out.println(users);
}
}
6.执行结果:
通过上面简单的几步我们就已经完成了mybatis与springboot的整合,那么这么简单的背后究竟有着怎样的秘密呢?下面我们将对其整合进行一个大概的剖析。
二、源码分析
按照springboot一贯的套路,我们先找到META-INF/spring.factories这个文件的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的信息。
打开我们可以看到:
这个圈红的就是mybatis与springboot整合的核心配置类,我们进去瞧一瞧:
我们可以看到这里是创建 SqlSessionFactory放入spring容器当中管理,从前面章节中我们都知道SqlSessionFactory是开启mybatis操作sql的重要的bean,我们下面来剖析下这个配置具体做了哪些,首先从spring中获取到DataSource,我们这里配置的DataSource是DruidDatasource,DataSourceConfiguration是springboot自动配置的,我们从DataSourceConfiguration可以找到如下配置:
这里可以看到spring.datasource.type这不就是我们上面demo配置application.yml里的么,我们配置的是com.alibaba.druid.pool.DruidDataSource,这里拿到了DataSource的配置信息之后接着调用initializeDataSourceBuilder:
调用initializeDataSourceBuilder构造了DataSourceBuilder,其初始化了数据就是配置文件的信息,包括url、用户名、密码、和type。接着调用build()通过反射进行生成DruidDataSource:
拿到了DataSource之后接着继续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)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
return factory.getObject();
}
构造SqlSessionFactoryBean,将DataSource放入,然后applyConfiguration里new Configuration()放入,通过mybatis源码看一遍就够了(3)| Configuration及解析配置文件我们都知道这个Configuration在mybatis中是很重要的一个角色。接着:
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
这里获取到的自然是我们前面配置文件配置的mapper文件
mybatis:
mapper-locations: classpath*:mybatis/*Mapper.xml
最后调用return factory.getObject():
afterPropertiesSet里调用了buildSqlSessionFactory(),buildSqlSessionFactory这里面代码太长我就截一些核心的部分代码:
targetConfiguration = this.configuration;
....
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));//构造Environment放入Configuration
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {//遍历所有mapper文件,也就是我们的UserMapper.xml
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());//构造XMLMapperBuilder,和前面章节中讲到的mybatis一致
xmlMapperBuilder.parse();//然后解析
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
部分解释已经写在上面代码注释,我们可以看到这里的操作是不是和我们前面章节mybatis源码看一遍就够了(3)| Configuration及解析配置文件分析的mybatis解析configuration一样,我们可以回忆下xmlMapperBuilder.parse():
这里就是解析完mapper文件然后生成MappedStatement注册到Configuration里的Map<String, MappedStatement> mappedStatements里面,我们前章节也已经讲解过,这里不再赘述。下面回到buildSqlSessionFactory的return,这里调用如下:
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
其实就是返回一个DefaultSqlSessionFactory对象,这就完成了mybatis的初始化工作,也完成了SqlSessionFactory的bean装入到spring去啦。
接着我们继续看我们的调用过程,其中UserMapper类中我们写了一个@mapper,这个是mybatis提供,我们都知道这个注解就是帮助自动扫描然后创建代理类,像第一章例子中mybatis源码看一遍就够了(1)| 前言的UserDao mapper = sqlSession.getMapper(UserDao.class);那么是什么时候帮我们做了这件事呢?下面进行分析
来看下这个AutoConfiguredMapperScannerRegistrar,这个是MybatisAutoConfiguration的内部类:
从上面截图可以看到构造BeanDefinitionBuilder,然后放入我们的@Mapper注解其实是放入MapperScannerConfigurer这个beanDefinition里,然后也放入需要扫描的包
然后向spring注册beanDefinition :MapperScannerConfigurer。MapperScannerConfigurer该类继承关系如下:
从继承关系可以看到它实现了BeanDefinitionRegistryPostProcessor,理解spring生命周期的我们都知道在bean进行注册过程会调用其postProcessBeanDefinitionRegistry,所以我们这里直接看他的实现方法:
scanner.registerFilters();设置扫描过滤条件:
这里可以清楚的看到扫描过滤条件就是包含有我们前面添加的@mapper这个注解的类,然后接着传入需要扫描的包名进行扫描操作scanner.scan,scan里调用doScan,ClassPathMapperScanner重写了doScan方法:
拿到扫描得到的@mapper所有类接着调用processBeanDefinitions,下面是该方法的核心部分:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
//为bean构造函数传入参数为beanClassName,这里自然是UserMapper
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
//这一行我们可以看到definition将其放入的是一个MapperFactoryBean.class,也就是这就是生成代理对象的关键所在
definition.setBeanClass(this.mapperFactoryBeanClass);
...省略部分代码
//构造函数自动注入,这也是注入SqlSessionFactory的关键所在
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
从上面代码和我加上的部分注释上来看,我们知道其实关键就在于MapperFactoryBean这个类,我们打开看下:
从该类继承关系看他继承了SqlSessionDaoSupport,以及实现了FactoryBean,FactoryBean有两个方法,一个是getObject返回对象,一个方法是getObjectType返回类型,不懂的可以自行百度,我这里就赘述了,下面是它的构造函数
这个构造函数传入的的自然就是我们前面 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 传入的,也就是UserMapper.class,接着是他实现的FactoryBean的两个方法:
从上面可以看到getObject返回的是从getSqlSession().getMapper(this.mapperInterface);获取的对象,那么这个getSqlSession()是什么,我们看下,他是SqlSessionDaoSupport的方法:
而这个sqlSessionTemplate是在其构造函数初始化:
这里可以看到,它的构造函数注入了SqlSessionFactory,这也就是我前面提到的definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);是注入SqlSessionFactory的关键,我们这里拿到在springboot创建好放入spring容器里的SqlSessionFactory,然后构造出sqlSessionTemplate,sqlSessionTemplate是一个sqlSession的实现类,所以这里也就构造拿到了sqlSession也就是说前面的getSqlSession().getMapper(this.mapperInterface);其实就是sqlSession.getMapper(UserMapper.class);这一步不就是前面mybatis源码看一遍就够了(2)| getMapper提到的吗,拿代理类操作嘛。也即是说UserMapper被我们赋予一个代理类并放在spring容器里。这样我们用的时候就是@Autowrite注入就ok了。
至此我们对mybatis的整个源码体系和原理以及包括整合springboot的源码分析和原理也就非常明朗啦,写到这里,感谢各位收看,如有什么不足的地方望指出,喜欢的可以关注一个,后续博主也会继续努力创作,写出更佳出色的博文。