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

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会做判断,条件不足时会抛出异常,因此实例化自然就失败了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值