springboot集成mybatis原理剖析

前言:本文主要基于springboot项目,从springboot的拓展角度来分析mybatis是如何与springboot集成的。

1、首先搭建springboot集成mybatis环境

1.1、maven pom文件依赖



    
        imalvisc-parent
        com.imalvisc
        1.0.0
    
    4.0.0

    spring-mybatis

    

        
        
            org.projectlombok
            lombok
        

        
        
            mysql
            mysql-connector-java
        

        
        
            com.alibaba
            druid-spring-boot-starter
        

        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
        

    

1.2、application.yml配置文件

###服务器配置
server:
  port: 9001

spring:
  application:
    name: user

  ###druid数据源配置
  datasource:
    name: dataSource
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/imalvisc?useSSL=false
    username: root
    password: imalvisc
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: select 1 from dual
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20
      filters: stat,wall
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

###mybatis数据源配置
mybatis:
  type-aliases-package: com.imalvisc.spring.mybatis.model
  configuration:
    map-underscore-to-camel-case: true

1.3、application启动类

package com.imalvisc.spring.mybatis;

import com.imalvisc.spring.mybatis.mapper.UserInfoMapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
@MapperScan(basePackages = {"com.imalvisc.spring.mybatis.mapper"})
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);
        UserInfoMapper userInfoMapper = applicationContext.getBean(UserInfoMapper.class);
        System.out.println(userInfoMapper.selectAll());
    }

}

1.4、启动测试

springboot集成mybatis原理剖析_第1张图片

以上就已经将springboot集成mybatis环境搭建完成。下面进行集成原理的解读。

2、springboot集成mybatis原理解读

2.1、在项目中引入了mybatis-spring-boot-starter依赖,mybatis-spring-boot-starter又引进了mybatis-spring-boot-autoconfigure依赖,在mybatis-spring-boot-autoconfigure的类路径下,可以看到META-INF目录下有一个spring.factories文件,文件里面的内容是org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration,这里的意思是springboot项目加载时,会扫描项目本身以及jar包类路径下的META-INF目录的spring.factories文件,然后会加载执行文件里面所配置到的类,同时这就是springboot自动配置的真谛所在。

springboot集成mybatis原理剖析_第2张图片

2.2、查看MybatisAutoConfiguration类发现,这是一个Configuration类,@AutoConfigureAfter(DataSourceAutoConfiguration.class)代表在DataSourceAutoConfiguration类加载完后再加载,这里是因为mybatis环境需要依赖数据源配置,所以必须在数据源配置完成后才进行mybatis配置。在MybatisAutoConfiguration类中,配置了SqlSessionFactory类,所在到这里mybatis的环境就已经生效了。

  @Bean
  @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.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

2.3、SqlSessionFactory配置完成后,剩下的就是扫描mapper接口并生成代理类存放到IOC容器中,这样就可以依赖注入Mapper了。

2.4、在Application启动类中,加上了@MapperScan(basePackages = {"com.imalvisc.spring.mybatis.mapper"})注解,打开MapperScan注解可以发现,MapperScan加上另一个注解@Import(MapperScannerRegistrar.class),@Import注解这里简单说明下其作用是spring环境监测到@Import注解时,会加载其指定的配置类,具体详细作用可以另查阅资料。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

2.5、查看MapperScannerRegistrar发现,其实现了ImportBeanDefinitionRegistrar接口,该接口的作用是spring会调用实现该接口的registerBeanDefinitions方法,传入AnnotationMetadata和BeanDefinitionRegistry两个参数,AnnotationMetadata的作用是封装了加上@Import注解的注解的属性,这里解析的可能有点绕口,举例说明就是@MapperScan注解加上了@Import注解,@MapperScan有一个basePackages属性,所以AnnotationMetadata封装了@MapperScan注解的basePackages属性的值。

2.6、MapperScannerRegistrar的registerBeanDefinitions方法中,获取了@MapperScan注解的属性后,调用了自身的重载registerBeanDefinitions方法,重载registerBeanDefinitions方法通过调用ClassPathMapperScanner的doScan方法就完成了Mapper的扫描并加入到spring容器中,到了这里整个mybatis的环境就完全生效了。doScan方法里面的深入逻辑这里就不展开了,需要继续深入剖析的同学可以另查阅资料。

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry);
    }
  }

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);

    Class annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value"))
            .filter(StringUtils::hasText)
            .collect(Collectors.toList()));

    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("basePackages"))
            .filter(StringUtils::hasText)
            .collect(Collectors.toList()));

    basePackages.addAll(
        Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
            .map(ClassUtils::getPackageName)
            .collect(Collectors.toList()));

    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

结语:mybatis集成到springboot中,主要是基于两大核心特性,一是springboot的自动配置特性,在路径下的META-INF目录的spring.factories文件配置需要加载的类,二是利用spring的@Import的ImportBeanDefinitionRegistrar特性,根据注解上指定的属性来加载配置。其实其它的技术(如Redis、RabbitMQ、JPA、Swagger等等)通过自动配置的方法集成到springboot环境中,基本都是通过这两种特性来实现,所以希望本文章不仅仅是帮助到同学们理解springboot集成mybatis的原理,更多的希望同学们可以理解springboot环境的拓展思路,以后写出更加优雅springboot拓展代码。

你可能感兴趣的:(springboot集成mybatis原理剖析)