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

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

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

@MapperScan@Mapper能一起用吗?

在讨论自动装配方式之前,先看看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的扫描分两种方式讨论

  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 doScan(String... basePackages) {
  // 扫描mapper(此时是原始对象)
  Set 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是没有效果的。

Original: https://www.cnblogs.com/konghuanxi/p/16277509.html
Author: 王谷雨
Title: Mybatis源码解读-SpringBoot中配置加载和Mapper的生成

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/620932/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Java学习 (八)基础篇 运算符

    运算符 基本运算符 1.一元基础运算(重点) 一元运算符 (a++ / ++a) (a– / –a) 2.二元基础运算 基础 计算返回值类型 关系运算 幂运…

    Java 2023年6月8日
    099
  • MinIO简介和java Api的使用

    MinIO是一个对象存储服务,非常轻量,也提供了多种语言的api,可以非常方便使用。 存储桶 minio中的bucket桶 MinIO将存储空间分为多个部分,称为bucket桶,文…

    Java 2023年6月6日
    088
  • Java UUID的底层原理

    UUID的几个核心特定: 全局时空唯一性固定长度128比特,也就是16字节(1 byte = 8 bit)分配速率极高,单机每秒可以生成超过1000万个UUID(实际上更高) UU…

    Java 2023年5月29日
    086
  • java 规则引擎资料汇集

    ibm的developworks中较早的一篇关于规则引擎的文章 https://www.ibm.com/developerworks/cn/java/j-java-rules/ 一…

    Java 2023年5月29日
    096
  • rocketmq总结

    1:角色关系 2:顺序消息 消费消息的顺序要同収送消息的顺序一致,在 RocketMQ 中,主要挃的是局部顺序,即一类消息为满足顺序性,必须 Producer 单线程顺序収送,丏収…

    Java 2023年5月30日
    092
  • springmvc静态资源配置

    <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>o…

    Java 2023年6月9日
    092
  • Stream流式计算

    Stream流式计算 集合/数据库用来进行数据的存储而计算则交给流 undefined public class Demo { public static void main(St…

    Java 2023年6月5日
    0103
  • 描述性统计

    Part2 描述性统计 一、直方图 直方图是用面积而不是用高度来表示数,所以其不同于条形图 左边的刻度表示该块 每单位所占总面积的百分比,可以称其为 密度尺度。例如以每50元为一个…

    Java 2023年6月7日
    093
  • Oracle数据库😊

    sqlplus常用命令 连接数据库:conn 用户名/密码@网络服务标识[as sysdba] 断开数据库连接: 断开和oracle的连接但是不退出sqlplus窗口 区别:sta…

    Java 2023年6月15日
    052
  • SpringBoot整合MongoDB

    NoSQL(Not Only SQL),即反SQL运动或者是不仅仅SQL,指的是非关系型的数据库,是一项全新的数据库革命运动,是一种全新的思维注入 NoSQL优点 数据库高并发读写…

    Java 2023年6月13日
    079
  • idea 全局maven默认 配置

    1.关闭项目 2.idea欢迎界面 自定义—》所有设置 配置maven地址 配置maven配置文件 配置存储仓库 这种方式还可以配置idea 其他全局默认属性,而不用一…

    Java 2023年6月6日
    094
  • Java-函数式编程大全指南

    前言 这里总结记录一下,所有java中可以用到的函数式编程以及使用场景和方法 Original: https://www.cnblogs.com/houzheng/p/160163…

    Java 2023年5月29日
    076
  • AxWebBrowser,WebBrowser

    利用2005的WebBrowser我暂时无法得到postData,不知道各位TX有没好办法,记得告知。 所以暂时使用AxWebBrowser, 下面是利用2005的WebBrows…

    Java 2023年6月13日
    087
  • 老码农的Java干货资源

    https://lingsui.github.io/ Original: https://www.cnblogs.com/zengkefu/p/9081860.htmlAuthor…

    Java 2023年5月29日
    090
  • 微信扫码登录

    微信扫码登录 1. 使用背景 如今开发业务系统,已不是一个单独的系统。往往需要同多个不同系统相互调用,甚至有时还需要跟微信,钉钉,飞书这样平台对接。目前我开发的部分业务系统,已经完…

    Java 2023年6月13日
    0255
  • Git命令集锦

    Git命令集锦 前言 正文 主体git操作 TEST 列出所有本地分支: git branch 列出所有远程分支: git branch -r 列出所有本地分支和远程分支: git…

    Java 2023年6月5日
    092
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球