源起

上篇我们讲到了MyBatis与SpringBoot的集成篇(一)—demo案例,在案例最后我们留下了一个疑问:mybatis-spring-boot-starter是何方神圣,功能是什么?为什么我们依赖了这个starter之后,开发变得简化了许多?

初识mybatis-spring-boot-starter

  • 该章节内容总结自官网
  • 官网传送门
  • The MyBatis-Spring-Boot-Starter help you build quickly MyBatis applications on top of the Spring Boot.
    By using this module you will achieve:
  • Build standalone applications
  • Reduce the boilerplate to almost zero
  • Less XML configuration
  • 上面的意思就是说:MyBatis-Spring-Boot-Starter能够帮助你快速的在SpringBoot的基础上构建一个MyBatis应用,简化你的xml配置等等;
  • MyBatis-Spring-Boot-Starter will:
  • Autodetect an existing DataSource
  • Will create and register an instance of a SqlSessionFactory passing that DataSource as an input using the SqlSessionFactoryBean
  • Will create and register an instance of a SqlSessionTemplate got out of the SqlSessionFactory
  • Auto-scan your mappers, link them to the SqlSessionTemplate and register them to Spring context so they can be injected into your beans
  • MyBatis-Spring-Boot-Starter将:
  • 自动检测现有的数据源
  • 将创建并注册一个实例SqlSessionFactory是通过数据源作为输入使用SqlSessionFactoryBean
  • 将创建并注册从SqlSessionFactory中获取的SqlSessionTemplate的实例
  • 自动扫描映射器,将它们链接到SqlSessionTemplate并将它们注册到Spring上下文,以便可以将它们注入到您的bean中
  • 通过上面的简介描述,我们不禁好奇,MyBatis-Spring-Boot-Starter是如何实现自动检测数据源,并构建SqlSessionFactory的,进而获取到SqlSession操作数据的;带着这个疑问,我们来看一下MyBatis-Spring-Boot-Starter的源码
  • 接下来我们从从github上将MyBatis-Spring-Boot-Starter的源码down下来。

源码探究

  • mybatis-spring-boot-starter既然能够无缝连接SpringBoot,那么我们猜想,他肯定是将自己融入与Spring的环境之中,一个是有一个入口,或者说是某一种加载机制,能让Spring去管理mybatis-spring-boot-starter,对此,我们可以联想到SPI
Java SPI机制
  • 首先我们需要简单了解一下SPI机制,SPI(Service Provider Interface)是Java语言中的一种服务发现机制;JDK提供的服务发现类java.util.ServiceLoader,里面约定了服务发现规则:
public final class ServiceLoader<S> implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

    //****以下省略****

}
  • 基于以上约定,当供应厂商提供了一个服务实现的时候,则需要在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件,文件内容即为实现类。比如:java.sql.DriverMysql服务实现,其中DriverJDK中提供的数据库驱动接口规范;

spring boot mybatis输出执行sql语句 mybatis spring boot starter_初始化

Spring SPI机制
  • 基于java的这种SPI思想,Spring制定了自己的SPI规范,org.springframework.core.io.support.SpringFactoriesLoader
public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

    private SpringFactoriesLoader() {
    }

    //****以下省略****
}
  • 这种自定义的SPI机制是Spring Boot Starter实现的基础。
mybatis-spring-boot-starter
  • 通过源码,我们看到mybatis-spring-boot-starter中是没有任何实现代码的,它是依赖了mybatis-spring-boot-autoconfigure的,所以这里我们直接看mybatis-spring-boot-autoconfigure中的META-INF/spring.factories,如图:

spring boot mybatis输出执行sql语句 mybatis spring boot starter_ide_02

  • 这里我们着重看org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration这个类,当SpringBoot启动的时候会自动扫描加载spring.factories文件,进而加载MybatisAutoConfiguration
MybatisAutoConfiguration
  • 接下来我们看一下MyBatisAutoConfiguration的源码

spring boot mybatis输出执行sql语句 mybatis spring boot starter_spring boot_03

  • 首先该类实现了org.springframework.beans.factory.InitializingBean接口,
  • InitializingBean接口为bean提供了初始化方法,凡是实现该接口的类,在初始化bean的时候会自动执行afterPropertiesSet方法。如下是MyBatisAutoConfiguration中实现的方法,执行了properties的校验,判断配置信息是否存在;
@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)");
    }
}
  • 此前我们分析过MyBatis初始化及会话工厂类、Sql执行过程等的源码,未看过的童鞋可查看下博主之前的博文;
  • 通过源码分析和使用的了解,我们知道每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的;所以MyBatisAutoConfiguration中必然有构建SqlSessionFactory的操作,我们翻看源码,果不其然:
@Bean
//如果不存在时创建SqlSessionFactory
@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();
}
  • 因为有@Bean注解的存在,所以Spring IoC容器在初始化的时候会加载执行该方法,从而构建出会话工厂SqlSessionFactory,既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。我们可以通过 SqlSession 实例来直接执行已映射的 SQL 语句;
  • Spring中对于SqlSession封装了一层:SqlSessionTemplateSqlSessionTemplate是线程安全的,所以一个实例可以被所有dao共享;

spring boot mybatis输出执行sql语句 mybatis spring boot starter_spring_04

总结

  • 阅读到此,相信你对mybatis-spring-boot-starter已经有了一定的了解了,他是一个使用Spring提供的扩展机制,封装了一些通用配置及操作,与SpringBoot结合,可以实现“即插即用”,降低了耦合度,简化开发,让程序员更专注与业务;
  • Spring号称粘合剂,能够很好的与各种插件进行集成开发,对此,很有很多东西值得我们去学习;
  • 此处我只是对mybatis-spring-boot-starter简单的做了剖析,不足之处,还望指正。