从源码看世界:Springboot整合Mybatis后到底做了什么

梦想与她 提交于 2020-02-25 16:09:12

Mybatis一次数据库操作过程的文章中,我展示了使用Mybatis操作数据库的demo,但实际使用时并不会这里写代码,因为一般都会使用springboot了,那现在我们一起来看看Springboot整合Mybatis之后到底为我们做了哪些事情。

要在Springboot整合Mybatis,首先修改pom依赖:

<!--<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.5.3</version>
</dependency>-->

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

然后在具体的mapper类中增加@Repository,让spring管理其实例;接着在Application增加@MapperScan("mapper所在的包路径")。

现在,只要在需要使用mapper的地方使用@Autowired自动注入即可使用:

@Autowired
private StudentMapper studentMapper;

@GetMapping("/test")
public String test() {
	Student student = studentMapper.getById(1);
	return student.getName();
}

至此,Springboot已经成功整合Mybatis,使用起来的确十分方便。但Mybatis的步骤肯定没有减少,只不过spring帮我们做了而已,那它到底在哪里自动完成的呢?

从引入的依赖命名来看,这个是mybatis开箱即用的starter(具体原理请查阅springboot的starter原理,此处不再赘述),因此首先查看spring.factory指定了哪些自动配置类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

第一个是动态语言驱动,主要看MybatisAutoConfiguration:里面注册了两个实例SqlSessionFactory与SqlSessionTemplate。注意,它们同时用了@ConditionalOnMissingBean注解,即没有这个bean的时候才生效,例如我们自己注册了SqlSessionFactory,因此MybatisAutoConfiguration的就不再实例化了。而SqlSessionTemplate是SqlSession的实现类,可以推测spring使用这个代替mybatis的DefaultSqlSession。

既然SqlSessionFactory和SqlSession都有了,那两者是如何关联起来的呢?

还记得上面我们用了@MapperScan来指定mapper所在的包路径吧,因此可以在这个注解作为入口,里面最重要的是@Import(MapperScannerRegistrar.class),而MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar,得到BeanDefinitionRegistry对象,然后根据@MapperScan的配置注册了beanClass=MapperScannerConfigurer的BeanDefinition。

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {

    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    // ...
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}

再来看看MapperScannerConfigurer,它实现了BeanDefinitionRegistryPostProcessor,也能得到BeanDefinitionRegistry对象,然后创建了ClassPathMapperScanner对象,从命名上看可以推测是扫描mapper用的,它继承了ClassPathBeanDefinitionScanner,同时重写了doScan方法:

  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
          + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

一旦扫描到需要实例化的侯选对象,就会进入processBeanDefinitions方法。这里细心的同学可能会问,到底哪些侯选对象对符合条件呢?ClassPathMapperScanner还重写了isCandidateComponent方法:

  @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

即既是接口又是独立类才符合条件。

在processBeanDefinitions最重要的作用是将侯选对象的beanClass改为MapperFactoryBean,从命名也可以推测是实例化Mapper的代理工厂,而它确实实现了FactoryBean,当mapper真正需要实例化的时候,就会调用MapperFactoryBean的getObject方法:

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

而getSqlSession()获得的正是SqlSessionTemplate,至此SqlSessionFactory和SqlSession就关联起来了。

最后,可能还有同学会问:难道既是接口又是独立类就作为mapper来处理了吗,不会代理错了吗?这个其实无需担心,因为MapperProxy会做判断,条件不足时会抛出异常,因此实例化自然就失败了

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!