從源碼看世界:Springboot整合Mybatis後到底作了什麼

Mybatis一次數據庫操做過程的文章中,我展現了使用Mybatis操做數據庫的demo,但實際使用時並不會這裏寫代碼,由於通常都會使用springboot了,那如今咱們一塊兒來看看Springboot整合Mybatis以後到底爲咱們作了哪些事情。java

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

<!--<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自動注入便可使用:springboot

@Autowired
private StudentMapper studentMapper;

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

至此,Springboot已經成功整合Mybatis,使用起來的確十分方便。但Mybatis的步驟確定沒有減小,只不過spring幫咱們作了而已,那它到底在哪裏自動完成的呢?mybatis

從引入的依賴命名來看,這個是mybatis開箱即用的starter(具體原理請查閱springboot的starter原理,此處再也不贅述),所以首先查看spring.factory指定了哪些自動配置類:app

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。ide

既然SqlSessionFactory和SqlSession都有了,那二者是如何關聯起來的呢?spring-boot

還記得上面咱們用了@MapperScan來指定mapper所在的包路徑吧,所以能夠在這個註解做爲入口,裏面最重要的是@Import(MapperScannerRegistrar.class),而MapperScannerRegistrar實現了ImportBeanDefinitionRegistrar,獲得BeanDefinitionRegistry對象,而後根據@MapperScan的配置註冊了beanClass=MapperScannerConfigurer的BeanDefinition。ui

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方法:this

@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會作判斷,條件不足時會拋出異常,所以實例化天然就失敗了

相關文章
相關標籤/搜索