mybatis源码分析系列:

  1. mybatis源码看这一遍就够了(1)| 前言
  2. mybatis源码看这一遍就够了(2)| getMapper
  3. mybatis源码看这一遍就够了(3)| Configuration及解析配置文件
  4. mybatis源码看这一遍就够了(4)| SqlSession.select调用分析
  5. 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.执行结果:

springboot mapper文件去掉黄色背景_spring boot

通过上面简单的几步我们就已经完成了mybatis与springboot的整合,那么这么简单的背后究竟有着怎样的秘密呢?下面我们将对其整合进行一个大概的剖析。

 

二、源码分析

按照springboot一贯的套路,我们先找到META-INF/spring.factories这个文件的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的信息。

springboot mapper文件去掉黄色背景_mybatis_02

打开我们可以看到:

springboot mapper文件去掉黄色背景_mybatis_03

这个圈红的就是mybatis与springboot整合的核心配置类,我们进去瞧一瞧:

springboot mapper文件去掉黄色背景_spring boot_04

我们可以看到这里是创建 SqlSessionFactory放入spring容器当中管理,从前面章节中我们都知道SqlSessionFactory是开启mybatis操作sql的重要的bean,我们下面来剖析下这个配置具体做了哪些,首先从spring中获取到DataSource,我们这里配置的DataSource是DruidDatasource,DataSourceConfiguration是springboot自动配置的,我们从DataSourceConfiguration可以找到如下配置:

springboot mapper文件去掉黄色背景_xml_05

这里可以看到spring.datasource.type这不就是我们上面demo配置application.yml里的么,我们配置的是com.alibaba.druid.pool.DruidDataSource,这里拿到了DataSource的配置信息之后接着调用initializeDataSourceBuilder:

springboot mapper文件去掉黄色背景_spring_06

 调用initializeDataSourceBuilder构造了DataSourceBuilder,其初始化了数据就是配置文件的信息,包括url、用户名、密码、和type。接着调用build()通过反射进行生成DruidDataSource:

springboot mapper文件去掉黄色背景_spring boot_07

 拿到了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());
    }

springboot mapper文件去掉黄色背景_mybatis_08

这里获取到的自然是我们前面配置文件配置的mapper文件

mybatis:
  mapper-locations: classpath*:mybatis/*Mapper.xml

最后调用return factory.getObject():

springboot mapper文件去掉黄色背景_mybatis_09

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():

springboot mapper文件去掉黄色背景_sql_10

这里就是解析完mapper文件然后生成MappedStatement注册到Configuration里的Map<String, MappedStatement> mappedStatements里面,我们前章节也已经讲解过,这里不再赘述。下面回到buildSqlSessionFactory的return,这里调用如下:

return this.sqlSessionFactoryBuilder.build(targetConfiguration);

springboot mapper文件去掉黄色背景_spring boot_11

其实就是返回一个DefaultSqlSessionFactory对象,这就完成了mybatis的初始化工作,也完成了SqlSessionFactory的bean装入到spring去啦。


接着我们继续看我们的调用过程,其中UserMapper类中我们写了一个@mapper,这个是mybatis提供,我们都知道这个注解就是帮助自动扫描然后创建代理类,像第一章例子中mybatis源码看一遍就够了(1)| 前言的UserDao mapper = sqlSession.getMapper(UserDao.class);那么是什么时候帮我们做了这件事呢?下面进行分析

来看下这个AutoConfiguredMapperScannerRegistrar,这个是MybatisAutoConfiguration的内部类:

springboot mapper文件去掉黄色背景_spring boot_12

从上面截图可以看到构造BeanDefinitionBuilder,然后放入我们的@Mapper注解其实是放入MapperScannerConfigurer这个beanDefinition里,然后也放入需要扫描的包

springboot mapper文件去掉黄色背景_xml_13

然后向spring注册beanDefinition :MapperScannerConfigurer。MapperScannerConfigurer该类继承关系如下:

springboot mapper文件去掉黄色背景_sql_14

从继承关系可以看到它实现了BeanDefinitionRegistryPostProcessor,理解spring生命周期的我们都知道在bean进行注册过程会调用其postProcessBeanDefinitionRegistry,所以我们这里直接看他的实现方法:

springboot mapper文件去掉黄色背景_mybatis_15

scanner.registerFilters();设置扫描过滤条件:

springboot mapper文件去掉黄色背景_spring_16

这里可以清楚的看到扫描过滤条件就是包含有我们前面添加的@mapper这个注解的类,然后接着传入需要扫描的包名进行扫描操作scanner.scan,scan里调用doScan,ClassPathMapperScanner重写了doScan方法:

springboot mapper文件去掉黄色背景_sql_17

拿到扫描得到的@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这个类,我们打开看下:

springboot mapper文件去掉黄色背景_xml_18

从该类继承关系看他继承了SqlSessionDaoSupport,以及实现了FactoryBean,FactoryBean有两个方法,一个是getObject返回对象,一个方法是getObjectType返回类型,不懂的可以自行百度,我这里就赘述了,下面是它的构造函数

springboot mapper文件去掉黄色背景_sql_19

这个构造函数传入的的自然就是我们前面 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 传入的,也就是UserMapper.class,接着是他实现的FactoryBean的两个方法:

springboot mapper文件去掉黄色背景_mybatis_20

从上面可以看到getObject返回的是从getSqlSession().getMapper(this.mapperInterface);获取的对象,那么这个getSqlSession()是什么,我们看下,他是SqlSessionDaoSupport的方法:

springboot mapper文件去掉黄色背景_mybatis_21

而这个sqlSessionTemplate是在其构造函数初始化:

springboot mapper文件去掉黄色背景_xml_22

这里可以看到,它的构造函数注入了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的源码分析和原理也就非常明朗啦,写到这里,感谢各位收看,如有什么不足的地方望指出,喜欢的可以关注一个,后续博主也会继续努力创作,写出更佳出色的博文。