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/583268/

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

(0)

大家都在看

  • Linux服务器配置git服务

    现在 Github 已经支持个人建立私有仓库,包括国内的一些开源平台如 Gitee 等也支持私有仓库,所以直接去建立私有仓库即可。或者可以自己买服务器搭建 GitLab。但是这篇文…

    Linux 2023年6月14日
    0100
  • JMeter压测出现“the target server failed to respond“的解决办法

    压测接口的时候,遇到了这个问题,在网上找到解决方案,试一下还挺管用,800并发没改前20%以上的报错率,改完800并发0.00%报错率。 感谢曲健老师的分享 解决方案如下: 修改执…

    Linux 2023年6月8日
    081
  • vim分屏功能总结

    vim的分屏功能总结起来,基本都是ctrl+w然后加上某一个按键字母,触发一个功能。(1)在shell里打开几个文件并且分屏:vim -On file1 file2 ……

    Linux 2023年6月14日
    089
  • 人人都写过的5个Bug!

    大家好,我是良许。 计算机专业的小伙伴,在学校期间一定学过 C 语言。它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理、操作系统、内存管理等等底层相关的知识会有更深入的了解,…

    Linux 2023年6月14日
    093
  • 009 Linux 文件大小统计与排序( du于df和sort)

    01 du 与 df 作用与区别? – du(disk usage) df(disk free) 02 du 常用命令示例 03 sort 常用参数 04 常用组合 d…

    Linux 2023年5月27日
    0152
  • 青春浙江微信平台如何退出?如何重新登录?微信如何清除浏览器缓存,如何清除浏览器cookies?

    青春浙江不能退出重新登录,有同学可能寻找解决方法,给大家贴出来:bug 解决办法:1. debugmm.qq.com/?forcex5=true 打开调试2. http://deb…

    Linux 2023年5月27日
    085
  • AIX系统NTP同步配置

    前言 当AIX系统的本地时间与时间服务器授出的标准时间误差大于±1000秒时。xntpd服务将无法同步时间并变得无法正常工作,请进行ntp配置前,先修改AIX系统的本地时间,尽量和…

    Linux 2023年6月6日
    098
  • 【凸优化】1 仿射集,凸集,锥

    1. 仿射集 Affine Sets 1)定义 定义1:(x_1, x_2)为集合(C\subseteq \mathbb{R}^n)中的任意两点,如果穿过(x_1,x_2)的 直线…

    Linux 2023年6月7日
    088
  • 我叫MongoDb,不懂我的看完我的故事您就入门啦!

    这是mongo基础篇,后续会连续更新4篇 大家好我叫MongoDb,自从07年10月10gen团队把我带到这个世界来,我已经13岁多啦,现在越来越多的小伙伴在拥抱我,我很高兴。我是…

    Linux 2023年6月14日
    0119
  • bat-Windows的文件夹备份

    REM program:用于HTS的Windows机器&#x5…

    Linux 2023年6月7日
    093
  • ret2syscall

    博客网址:www.shicoder.top微信:18223081347欢迎加群聊天 :452380935 这一次我们来深入分析下更难的栈溢出题目 ret2syscall 首先还是先…

    Linux 2023年6月13日
    0112
  • dbus的奇妙世界

    故事背景 在linux开发中我们经常会用到dbus来进行进程间通信,但是如何理解dbus服务端和客户端呢?很多小伙伴可能都会遇到类似的问题,而且都是含含糊糊的,接下来我们直接上硬菜…

    Linux 2023年5月27日
    081
  • 扑克牌大小—牛客网

    扑克牌大小_牛客题霸_牛客网 (nowcoder.com) #include #include<string> #include using namespace std…

    Linux 2023年6月13日
    0113
  • VMware ESXi 7.0 Update 3c SLIC 2.6 & Unlocker (2022 U3 Refresh)

    提供标准版和 Dell (戴尔)、HPE (慧与)、Lenovo (联想)、Inspur (浪潮)、Cisco (思科) 定制版镜像 请访问原文链接:VMware ESXi 7.0…

    Linux 2023年5月27日
    083
  • 005.系统管理监测命令

    作者:木二 出处:http://www.cnblogs.com/itzgr/ 关于作者:云计算、虚拟化,Linux,多多交流! 本文版权归作者所有,欢迎转载,但未经作者同意必须保留…

    Linux 2023年6月7日
    092
  • AWS修改RDS时区

    查看 RDS 当前时区 默认情况下,AWS 的 RDS 采用的是 UTC 时间。而我们地区一般位于东八区,因此我们本地的时间是 UTC+8。 连接到 RDS 上,查询当前实例的时区…

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