Mybatis源码解读-SpringBoot中配置加载和Mapper的生成

2022年6月16日 444点热度 0人点赞 0条评论

本文mybatis-spring-boot探讨在springboot工程中mybatis相关对象的注册与加载。

建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此文章。

传送门:Mybatis源码解读-配置加载和Mapper的生成

问题

@MapperScan@Mapper能一起用吗?

使用

  1. 创建工程不再赘述,参考demo
  2. 编写Mapper

    Mapper的注册有两种方式:

    • 在Mapper添加@Mapper注解
    • 在Application类添加@MapperScan注解确定扫描包路径

    后面会讲解这两种方式的区别

****SqlSessionFactory和SqlSession****

在讨论自动装配方式之前,先看看mybatis最简洁的demo

public static void main(String[] args) throws Exception {
    // 配置文件路径
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 1.读取配置,创建SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 2.通过工厂获取SqlSession
    SqlSession session = sqlSessionFactory.openSession();
    try {
        // 3.获取mapper代理对象
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        // 4.执行查询,此处才真正连接数据库
        System.out.println(mapper.selectByName("张三"));
    } finally {
        // 5.关闭连接
        session.close();
    }
}

可以看到,首先需要创建SqlSessionFactory和SqlSession,在springboot中,这两者通过自动装配完成。

在mybatis-spring-boot-autoconfigure-x.x.x.jar的spring.factories中,可以看到自动装配注入了MybatisAutoConfiguration

public class MybatisAutoConfiguration implements InitializingBean {
  ......
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    ......
  }

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }
}

SqlSessionTemplateSqlSession的子类,所以现在二者都有了。

Mapper

Mapper的扫描分两种方式讨论

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

    可以看到,导入了MapperScannerRegistrar类

    public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes
            .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        if (mapperScanAttrs != null) {
          registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
              generateBaseBeanName(importingClassMetadata, 0));
        }
      }
    
      void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
          BeanDefinitionRegistry registry, String beanName) {
    
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        ......
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
      }
    }
    

    因为MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,所以会被调用registerBeanDefinitions方法,最后注册MapperScannerConfigurer

    咱们先记住MapperScannerConfigurer这个类,去看看@Mapper的方式

  2. @Mapper方式

    MybatisAutoConfiguration中,有这么一段代码

    @org.springframework.context.annotation.Configuration
    // 如果满足条件,则导入AutoConfiguredMapperScannerRegistrar
    @Import(AutoConfiguredMapperScannerRegistrar.class)
    // 如果MapperFactoryBean和MapperScannerConfigurer都没注册,则满足条件
    @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
    public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
    
      ......
    
    }
    

    我们在@MapperScan方式看到,是已经注册了MapperScannerConfigurer类的。所以,@MapperScan会覆盖@Mapper

    继续看看AutoConfiguredMapperScannerRegistrar

    public static class AutoConfiguredMapperScannerRegistrar
          implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
    
      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
        ......
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        builder.addPropertyValue("annotationClass", Mapper.class);
        ......
    
        registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
      }
    
      ......
    }
    

    可以看到,同样是注册了MapperScannerConfigurer

    也就是两种注解方式都是通过MapperScannerConfigurer扫描mapper注册的

  3. 通用部分

    继续追踪MapperScannerConfigurer的调用链

    // MapperScannerConfigurer#postProcessBeanDefinitionRegistry
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
      ......
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
      ......
      // 注册过滤器(@Mapper和@MapperScan的区别体现在这里)
      scanner.registerFilters();
      // 开始扫描bean
      scanner.scan(
          StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }
    
    public void registerFilters() {
      boolean acceptAllInterfaces = true;
    
      // 如果指定了扫描类型(@Mapper走这里)
      // annotationClass在前面的AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions被注入
      // 就是这段builder.addPropertyValue("annotationClass", Mapper.class);
      if (this.annotationClass != null) {
        addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
        acceptAllInterfaces = false;
      }
    
      ......
      // 如果没指定扫描类型,则扫描全部(@MapperScan走这里)
      if (acceptAllInterfaces) {
        addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
      }
    
      // exclude package-info.java
      addExcludeFilter((metadataReader, metadataReaderFactory) -> {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
      });
    }
    

    看完了过滤器的注册,继续回到扫描逻辑scanner.scan

    // ClassPathMapperScanner#scan(String... basePackages) -->
    // ClassPathMapperScanner#doScan(String... basePackages)
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
      // 扫描mapper(此时是原始对象)
      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 {
        // 通过MapperFactoryBean类将mapper对象转换成代理对象MapperProxy
        processBeanDefinitions(beanDefinitions);
      }
    
      return beanDefinitions;
    }
    

答案

@MapperScan@Mapper能一起用(不会报错),但是@Mapper是没有效果的。

王谷雨

一个苟且偷生的java程序员

文章评论