MyBatis接口是如何执行SQL的?- 自动配置篇

554 阅读3分钟

前面文档(Mybatis源码解析-快速一览)我简单快速的描述了Mybatis的一个整体的源码流程,本文主要聊一下Mybatis一个热门的面试题:MyBatis接口是如何执行SQL的?

网上很多都是基于纯粹mybatis的,我这里主要还是出发点在我们常用的SpringBoot项目中,是如何让我们只通过接口就能去执行到对应的SQL的。

spring为我们整合了很多第三方的starter自动装配来用于快速集成,mybatis自然也不例外,我们分析的入口也就是基于mybatis-spring-boot-starter为例,这里基于当前最新的依赖版本2.1.4为例进行分析。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>

这个包里面有啥?

image-20210804211141047.png 是一个空包,只是用来指定一些依赖(这是spring官方提供的starter的一个规范,具体自动配置其实都在mybatis-spring-boot-autoconfigure里面)。

这里面引入了3个比较重要的包:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
  </dependency>
</dependencies>

这三个包的作用分别是:

  • mybatis-spring-boot-starter:只是负责整合依赖关系,类似于parent。
  • mybatis-spring-boot-autoconfigure:只是负责自动配置的工作。
  • mybatis-spring:负责spring和mybatis结合的核心部分,重点也就是这个部分。

我们先看自动配置(mybatis-spring-boot-autoconfigure-2.1.4.jar包内),这里面选中的是关键的类。

image-20210804211911412.png

  1. 先看spring.factories:

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
    org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
    
  2. 可以见得重点是MybatisAutoConfiguration,该类实现了mybatis的自动配置,而具体的配置可选项其实就是MybatisProperties的内容。

  3. 我们看看这块代码:

    @Configuration
    @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
    @ConditionalOnSingleCandidate(DataSource.class)
    @EnableConfigurationProperties({MybatisProperties.class})
    @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
    public class MybatisAutoConfiguration implements InitializingBean {
    
        private final MybatisProperties properties;
        ...
    
        @Bean
        @ConditionalOnMissingBean
        public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
          	SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
            //☆ 中间无非一些配置工作,将properties以及一些datasource相关的数据设置进SqlSessionFactoryBean对象。
            return factory.getObject();
        }
    
        @Bean
        @ConditionalOnMissingBean
        public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {...}
    
        @Configuration
        @Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
        @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
        public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
            public MapperScannerRegistrarNotFoundConfiguration() {
            }
    
            public void afterPropertiesSet() {
                MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
            }
        }
    }   
    
    • 可以看到这里创建了SqlSessionFactorySqlSessionTemplate这2个核心的bean而构建SqlSessionFactory的核心是靠 SqlSessionFactoryBean这个类的,而这个类是在mybatis-spring-2.0.6.jar包下
  4. 我们知道在纯Spring和Mybatis的场景下,我们是这么获取Mapper来使用的:

    Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    XXMapper mapper = sqlSession.getMapper(XXMapper.class);
    

    只不过,这部分mybatis-config.xml的配置现在交给spring进行自动装配了。sqlSessionFactory的构建工作是由SqlSessionFactoryBuilder来构建的,所以springboot中也不例外,唯一不同的是:上述方法是用的SqlSessionFactory.build(Reader reader)方法,而springboot自动配置是使用的SqlSessionFactoryBuilder.build(Configuration config),configuration对象是我们自己创建好了。

    // 这里创建了SqlSessionFactoryBuilder,调用mybatis的builder来创建SqlSessionFactory
    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();    
    private SqlSessionFactory sqlSessionFactory;
    /**
     * 只挑选了核心的方法
     */
    @Override
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            afterPropertiesSet();
        }
        return this.sqlSessionFactory;
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
    		...
        this.sqlSessionFactory = buildSqlSessionFactory();
    }
    
    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        final Configuration targetConfiguration;
        XMLConfigBuilder xmlConfigBuilder = null;
        // 构建configuration
        if (this.configuration != null) {
            targetConfiguration = this.configuration;
            if (hasLength(this.typeAliasesPackage)) { }
            if (!isEmpty(this.typeAliases)) { }
            if (!isEmpty(this.plugins)) { }
            if (hasLength(this.typeHandlersPackage)) { }
            if (!isEmpty(this.typeHandlers)) { }
            targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
            Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
            if (this.mapperLocations != null) { }
            return this.sqlSessionFactoryBuilder.build(targetConfiguration);
        }
    }
    

上面这是解析过程,目的就是创建SqlSessionFactory,其逻辑对应 Mybatis源码解析-快速一览的解析过程,这里就不细说了。

自动配置完成后,容器中会注入SqlSessionTemplate的实例bean(一个线程安全的SqlSession实现,主要基于ThreadLocal)。并且我们的mapper.xml里面的内容会解析并保存到configuration的mappedStatements的一个map中,以及将mapper.xml里

<mapper namespace="work.lollipops.demo.mybatis.mapper.TB1Mapper">

解析配置到mapperRegistry的knownMappers(也是一个map)里面。

这2个东西很有用,mybatis所有的方法执行都将主要围绕这2个打交道。