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)

大家都在看

  • rsync

    rsync简介 rsync是linux系统下的数据镜像备份工具。使用快速增量备份工具Remote Sync可以远程同步,支持本地复制,或者与其他SSH、rsync主机同步。 rsy…

    Linux 2023年6月6日
    089
  • 如何配置静态路由

    1.主机A想要和主机B 进行通讯,首先会发送一个ARP的广播。 2.第一次封装包含:源IP(192.168.1.2)目的IP(192.168.2.2);源Mac(11-11)目的M…

    Linux 2023年6月6日
    0116
  • EhCache缓存页面、局部页面和对象缓存

    页面缓存:SimplePageCachingFilter web.xml <filter> <filter-name>PageEhCacheFilterfi…

    Linux 2023年6月13日
    0106
  • 【Linux】在Linux下文件io使用(二)

    在linux下,一切皆文件。当文件被打开时,会返回文件描述符用于操作该文件,从shell中运行一个进程,默认会有3个文件描述符存在(0、1、2); 0表示标准输入,1表示标准输出,…

    Linux 2023年6月13日
    0117
  • Linux常用磁盘管理命令详解

    du du命令用于查看文件和目录磁盘的使用空间。 命令语法: du [&#x53C2;&#x6570;] [&#x6587;&#x4EF6;&amp…

    Linux 2023年5月27日
    0113
  • Podman基础用法

    Podman基础 1、什么是Podman? Podman是一种开源的Linux原生工具,旨在根据开放容器倡议(Open Container Initiative,OCI)标准开发、…

    Linux 2023年6月7日
    095
  • XShell实现自动化执行脚本.sh文件)(网络安全检查)

    1、自动化登录服务器操作: 第一种方式:(login.vbs文件) Sub Mainxsh.Screen.Send “ssh root@10.99.202.54&#82…

    Linux 2023年5月28日
    083
  • 一文说清OpenCL框架

    背景 Read the fucking official documents! –By 鲁迅 A picture is worth a thousand words. …

    Linux 2023年6月8日
    0116
  • 5.1 Vim及其安装

    通过前面的学习我们知道,Linux 系统中”一切皆文件”,因此当我们在命令行下更改文件内容时,不可避免地要用到文本编辑器。 作为一名 Linux 初学者,你…

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

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

    Linux 2023年6月8日
    088
  • Nginx几种负载均衡方式介绍

    Nginx几种负载均衡方式介绍 前言 负载均衡就是Nginx将请求分摊到不同的服务器中,保证服务的可用性,缓解服务压力,保证服务的响应速度,即使某一个应用服务不可用,也可以保证业务…

    Linux 2023年6月6日
    0117
  • markdown插入代码块导致列表缩进异常的解决方法

    前言 一、问题描述 总结 前言 最近正在用markdown写博客,发现插入代码块后,代码块和代码块后的内容会产生缩进异常,翻了翻网页,发现又是经典的一模一样的文章一大把,尝试了之后…

    Linux 2023年6月7日
    0197
  • ServiceHub.DataWarehouseHost.exe内存泄漏问题的处理

    ServiceHub.DataWarehouseHost.exe内存泄漏问题的处理。 Visual Studio 2017的15.2版本在debug应用程序时,ServiceHub…

    Linux 2023年6月7日
    0112
  • MSSQL中完整备份及完整还原的T-SQL实践

    | 0.37分钟 | 596.8字符 | 1、引言&背景 2、完整备份 3、完整还原 4、声明与参考资料 | SCscHero | 2022/5/27 AM12:47 | …

    Linux 2023年6月14日
    086
  • 解决Ubuntu(20.04)和Windows10双系统时间不同步问题

    1. 原因分析 出现这种情况的原因是 Windows 和 Ubuntu它们在默认情况下看待硬件时间(主板上的BOIS显示的时间)的方式 不一样。 我们先来看看时间的概念: [En]…

    Linux 2023年5月27日
    0195
  • 关于格物致知

    格物致知: “格物,致知,诚意,正心,修身,齐家,治国,平天下”是孔子学生曾子所著《礼记.大学》里的八条目,而”格物致知”更是儒学思…

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