【Mybatis源码解析】mapper实例化及执行流程源码分析
文章目录
- 简介
- 环境搭建
- 源码解析
基础环境:JDK17、SpringBoot3.0、mysql5.7
储备知识:《【Spring6源码・AOP】AOP源码解析》、《JDBC详细全解》
简介
基于SpringBoot的Mybatis源码解析:
1.如何对mapper实例化bean
在加载BeanDefinition时,会将SqlSessionFactory、SqlSessionTemplate、MapperScannerConfigurer加载到注册表中,以供后续进行实例化。
而且在此期间,mapper接口已经实例化完成了,后续从缓存中取出即可。
初始化时,
第一步,使用SqlSessionFactoryBean来生成SqlSessionFactory。生成过程中,使用了MapperAnnotationBuilder解析mapper接口上的注解,放到Configuration中,然后放到SqlSessionFactory里,把创建的SqlSessionFactory实例放到bean缓存池中。
第二步,使用使用SqlSessionTemplate构造器创建SqlSessionTemplate对象,其中用了jdk代理方式创建了SqlSession代理对象。需说明,SqlSessionTemplate采用单例模式,并通过TransactionSynchronizationManager中的ThreadLocal<Map<Object, Object>>保存线程对应的SqlSession(即DefaultSqlSession,这个不是线程安全的),实现session的线程安全。
第三步,通过MapperFactoryBean来实例化mapper接口,也是通过jdk代理方式创建的mapper代理对象,并把依赖的SqlSessionFactory和SqlSessionTemplate注入mapper中。
2.如何执行mapper
执行mapper方法的过程,主要是先通过两个代理类,即先执行mapper代理实现类MapperProxy的invoke方法,然后执行SqlSessionTemplate代理实现类的invoke方法,然后进入DefaultSqlSession相应方法中,这里会根据mapper的限定名获取MappedStatement,然后调用Executor相应方法,而Executor是封装了jdbc的操作,所以最终是通过jdbc执行sql,最后再把执行的结果解析返回。
在spring容器初始化的过程中使用JDK动态代理生成mapper的代理对象,然后在执行mapper方法的过程,利用代理机制,执行目标方法,最终底层通过jdbc执行sql。
附:
SqlSessionFactoryBean:用于生成SqlSessionFactory 的FactoryBean。
Configuration:存放所有mybatis配置信息,包括mapper接口、mapper.xml、mybatis-config.xml等;
XMLConfigBuilder: 解析 mybatis-config.xml 配置并存放到Configuration中;
XMLMapperBuilder: 解析 mapper.xml配置并存放到Configuration中,在这里完成了mapper接口与mapper.xml的绑定;
MapperAnnotationBuilder:解析mapper接口上的注解,将sql信息存放到configuration中;
SqlSessionFactoryBuilder: 实际用于创建 SqlSessionFactory
SqlSessionFactory: 用于创建 SqlSession
SqlSession: Mybatis工作的最顶层API会话接口,所有访问数据库的操作都是通过SqlSession来的。
SqlSessionTemplate: 内部维护有 SqlSession 的代理对象,解耦Mapper和SqlSession的关键对象。
MapperScannerConfigurer:用于扫描所有mapper接口,并将mapper接口生成beanDefinition放到beanFactory的bean定义注册表中,然后再把beanDefinition中的mapper的beanClass转换成MapperFactoryBean,这么做是为了:第一,可以通过遍历bean定义注册表,找到mapper的beanDefinition,用于实例化bean;第二,可以通过MapperFactoryBean的getObject方法来实例化bean(通过jdk代理生成了bean的代理对象)。
环境搭建
依赖:
<!-- MySQL驱动 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 集成MyBatis -->
<!-- 引入 3.0.0 版本的 mybatis-spring-boot-starter(正式版) -->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.0</version>
</dependency>
mapper:
public interface UserMapper {@Select("select * from user where id = #{id}")User select(String id);
}
controller:
main:
@SpringBootApplication
@MapperScan(basePackages = "com.ossa.web3.mapper")
public class AppRun {public static void main(String[] args) {SpringApplication.run(AppRun.class, args);}
}
application.yml
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://issavior-aliyun-rds.mysql.rds.aliyuncs.com:3306/test?useUnicode=true&charsetEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=trueusername: rootpassword: root
访问:http://localhost:8080/user/test
响应结果:{"id":"c5329f3b-3e98-4722-8faf-e87d9b981871","name":"Marry","age":18}
源码解析
因为项目引入了mybatis-spring-boot-starter
依赖,此依赖又依赖了mybatis-spring-boot-autoconfigure
,根据SpringBoot可以自动装配的机制,会扫面所有包下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件,然后加载其中的类封装成BeanDefinition,当然加载之前会通过spring-autoconfigure-metadata.properties
配置文件进行条件判断。判断是否要加载其中的类。
看看此META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件:
总共导入两个类:MybatisLanguageDriverAutoConfiguration、MybatisAutoConfiguration。
先看imports文件中的MybatisAutoConfiguration类,这个类会在封装BeanDefinition的时候加载:
分析一下配置类的注解:
@org.springframework.context.annotation.Configuration
:
配置类@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnClass注解的作用是当项目中存在某个类时才会使标有该注解的类或方法生效,底层是通过Class.forName()
判断是否存在该类。@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnSingleCandidate表示当指定Bean只有一个,或者虽然有多个但是指定首选Bean,这时候才会将其放到容器中。@EnableConfigurationProperties(MybatisProperties.class)
将properties和yml配置文件属性转化为bean对象使用。@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
@AutoConfigureAfter 在加载配置的类之后再加载当前类
看一下Mybatis的配置属性:
@EnableConfigurationProperties(MybatisProperties.class)
原理:其扫描配置文件,根据prefix匹配对应属性,然后填充。
都有注释,大家可以自己看:
通过之前对Spring和SpringBoot的源码分析,我们可知:自动装配操作在组件加载之后,所以,我们先来看看启动类上的注解:
这个@MapperScan注解用来扫描相关mapper接口,并生成对应的代理对象。
看一下类的相关介绍:
该注解上有一个@Import(MapperScannerRegistrar.class)
注解,意味着在启动类加载的同时,会将此注解后的类MapperScannerRegistrar加载进IOC容器。
步入MapperScannerRegistrar:
该类实现了ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法。
既然该类实现了ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法。
那么就会在如下这里进行加载:
最后会执行该类实现后的方法:
总结一句话:@MapperScan通过@Import引入MapperScannerRegistrar类,MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行MapperScannerRegistrar#registerBeanDefinitions方法。
这个registerBeanDefinitions方法首先通过上面72行代码获取获取@MapperScan注解属性信息:
步入registerBeanDefinitions方法:
首先使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象。
之后,将注解属性中的值赋给builder对象。
最后注册该BeanDefinition:此时MapperScannerConfigurer对象已经注入IOC容器了
,这里划重点,一会要用到。
顺便看一下MapperScannerConfigurer这个类:
这个类实现了BeanDefinitionRegistryPostProcessor接口,在bean的生命周期中会调用其postProcessBeanDefinitionRegistry方法:
在invokeBeanFactoryPostProcessors方法里会执行两个接口,按先后执行顺序为:
第一个是BeanDefinitionRegistryPostProcessor;
第二个是BeanFactoryPostProcessor。
步入postProcessBeanDefinitionRegistry方法:
首先构建ClassPathMapperScanner对象,然后填充属性。
再通过下面两行代码,调用scan方法。
// 注册Filter,因为上面构造函数我们没有使用默认的Filter,
// 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
scanner.registerFilters();
// 扫描basePackage,basePackage可通过",; \t\n"来填写多个,
// ClassPathMapperScanner重写了doScan方法
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
步入scan方法:
步入doScan方法:
进入父类:
这个我们之前讲过,扫包,解析,存入注册表中,返回。
返回:
步入processBeanDefinitions方法:
在这个processBeanDefinitions方法中,把每个mapper的Bean定义的BeanClass设置为mapperFactoryBeanClass,这样做是为了让后面创建bean时,可以使用MapperFactoryBean来创建bean。这里为什么要把mapper的BeanClass设置为mapperFactoryBeanClass,因为mapper是接口,接口不能实例化,所以mybatis中就把mapper的beanDefinition的beanClass定义为mapperFactoryBeanClass,利用mapperFactoryBeanClass是通过getObject()来进行实例化,即通过jdk代理的方式,生成的代理对象。
到这里mapper就扫描完事了, 处理用@MapperScan注解扫描,还可以在mapper类上加上@Mapper注解扫描。
会通过MapperScannerRegistrarNotFoundConfiguration这个配置类,导入AutoConfiguredMapperScannerRegistrar类,AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行AutoConfiguredMapperScannerRegistrar.registerBeanDefinitions。进而生成MapperScannerConfigurer的bean定义,放到IOC容器(beanFactory)中,后面的逻辑和@MapperScan是一样的。
当然,如果用了@MapperScan这个注解,是不会加载MapperScannerRegistrarNotFoundConfiguration这个配置类的,因为这个类上有一个注解:@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
,如果当前容器中存在MapperScannerConfigurer这个类,这个配置类就不会生效,上面我们已经加载过了,不信往上翻翻,我还做了标记。
提一嘴这个@ConditionalOnMissingBean注解,这个注解底层有一个搜索策略SearchStrategy ,最终会返回true和false。
都添加进注册表后,会对bean进行实例化和初始化,初始化又会进行属性填充,按着正常的业务逻辑:
controller依赖的service,service依赖mapper,mapper依赖sqlSessionTemplate,sqlSessionTemplate依赖SqlSessionFactory,所以最终会先实例化SqlSessionFactory。
在MybatisAutoConfiguration这个配置类中,@Bean声明sqlSessionFactory方法:
首先获取配置文件和mapper对应的文件,最后返回SqlSessionFactory,如果没有则回去创建。
buildSqlSessionFactory通过XMLConfigBuilder解析mybatis配置,通过XMLMapperBuilder解析mapper.xml的配置,然后生成mappedStatements、resultMaps、sqlFragments,以及其他的配置,最终放到Configuration里,供后面使用。
紧接着实例化SqlSessionTemplate,
最后会调用SqlSessionTemplate构造方法,用JDK动态代理创建对象。
最后到实例化maper了。
回到initializeBean方法:
在前面,bean已经被替换成MapperFactoryBean。
MapperFactoryBean实现了InitializingBean接口,所以先执行afterPropertiesSet,最终执行MapperFactoryBean.checkDaoConfig。
步入checkDaoConfig方法:
首先检查配置,mapper接口:
78行代码,如果configuration中没有该mapper接口,则加载:因为有关sql有两种写法,一种是我们这个demo这种注解形式,一种就是xml文件写sql这种方式,如果是xml这种,xmlMapperBuilder.parse()就会加载mapper接口,这里就不会进入。如果使用注解这种方式,就会进入这里。
在里面解析了mapper接口上的注解,然后填充configuration。
步入addMapper方法:首先判断是否为接口,再构建MapperAnnotationBuilder解析器,再去解析。
步入parse方法:
步入parseStatement方法:
这里根据userMapper接口,解析接口上的注解。
填充完configuration之后,基本就加载差不多了。
我们来看一下mapper接口的执行流程:
发送请求:http://localhost:8080/user/test
进入断点:
步入selectById方法:进入了mapper代理类的invoke方法中:
最终执行execute方法:根据增删改查四种操作继续接下来的逻辑:
我们这里是SELECT:获取参数,调用selectOne方法:
步入selectOne方法:
因为sqlSessionTemplate是SqlSessionInterceptor代理创建的,所以,接下来走SqlSessionInterceptor.invoke方法。
创建sqlSession,执行代理的真正的方法,如果被事务管理,则提交事务,最后关闭sqlSession。
步入selectOne方法:
步入selectList方法:
最终调用:首先获取Statement,再去调用执行器的查询方法:
最终会调用query方法:预编译,执行sql。再调用handleResultSets方法处理结果并返回。
相关文章:

【Mybatis源码解析】mapper实例化及执行流程源码分析
文章目录简介环境搭建源码解析基础环境:JDK17、SpringBoot3.0、mysql5.7 储备知识:《【Spring6源码・AOP】AOP源码解析》、《JDBC详细全解》 简介 基于SpringBoot的Mybatis源码解析: 1.如何对mapper实例化bean 在加载BeanDefinition时&a…...

分布式文件管理系统(MinIO)
1.去中心化,每个点是对等的关系,通过Ngix对负载做均衡工作。 好处: 能够避免单点故障,将多块硬盘组成一个对象存储服务。 2. 使用纠删编码技术来保护数据,是一种回复丢失和损坏的数据的数学算法,他将数据分…...

Springcloud-配置中心config
一、添加依赖<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-config-server</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId&…...

[项目篇] 音乐播放器开发报告
文章目录1. 项目描述:2. 项目上线展现:3. 项目具体实现:1. 登录2. 注册3.退出系统4.添加音乐4.1前后端交互约定4.2上传文件业务逻辑:4.3创建model包中的music类4.4在MusicMapper接口中,声明insertMusic抽象方法4.5在mybatis包中添…...

Spring Cloud Alibaba--gateway微服务详解之网关(二)
1、网关介绍 上篇对微服务中的nacos注册中心进行集成讲解。nacos主要作用是管理多服务之间复杂关系的组件。微服务是非常庞大且问题突出的架构,HTTP协议具有跨源资源共享 (CORS) Cross- Origin Resource Sharing机制,而处于安全考虑往往前端架构都会对跨…...

Zynq非VDMA方案实现视频3帧缓存输出,无需SDK配置,提供工程源码和技术支持
目录1、前言2、VDMA的不便之处3、FDMA取代VDMA实现视频缓存输出4、Vivado工程详解5、上板调试验证并演示6、福利:工程代码的获取1、前言 对于Zynq和Microblaze的用户而言,要想实现图像缓存输出,多半要使用Xilinx推荐的VDMA方案,该…...

血液透析过滤芯气密性检测装置中的高精度多段压力控制解决方案
摘要:针对目前血液过滤芯气密性检测过程中存在的自动化水平较低、多个检测压力之间需人工切换和压力控制精度较差的问题,为满足客户对高精度和自动化气密性检测的要求,本文提出了相应的解决方案。解决方案的主要特点是全过程的可编程压力控制…...

PDF加密如何批量解除?快来了解下这个方法
在现代办公环境中,PDF文档的使用非常普遍。然而,由于一些安全需求,有时候PDF文档会被加密,使得只有授权人员可以查看或修改它。但是,如果您需要对许多加密PDF文档进行操作,逐个解密这些文档可能非常费时费力…...

C++——哈希4|布隆过滤器
目录 布隆过滤器 完整代码 布隆过滤器应用 布隆过滤器的查找 布隆过滤器删除 布隆过滤器优点 布隆过滤器缺陷 布隆过滤器海量数据处理 布隆过滤器 位图只能映射整形,而对于字符串却无能为力。 把字符串用哈希算法转成整形,映射一个位置进行标…...

python冒号的用法总结
一维数组 1. 单个冒号的情况 1.1 写完整的情况下 单个冒号的情况下,对数组的遍历操作是从前向后操作。如:arr[a:b] ,冒号前的a含义是从a开始遍历,冒号后的b含义是到b截止(不包括b)。 arr [1, 2, 3, 4,…...

面试题整理
面试题整理 一、Java基础 1、Java 语言有哪些特点 简单易学; 面向对象(封装,继承,多态); 平台无关性( Java 虚拟机实现平台无关性); 支持多线程( C 语言…...

C语言深度解剖-关键字(7)
目录 switch case 语句 理解: 补充: 深入理解: default 语句: case语句: 总结: do、while、for 关键字 while for do while 各种死循环方法: while for do while getchar 写在…...
利用JavaScript编写Python内置函数查询工具
最近我开始学习Python编程语言,我发现Python拥有非常丰富的内置函数,可以用来实现各种不同的功能。但是每当我需要查找一个内置函数时,我总是需要联网使用搜索引擎进行查询。这种方式不仅费时费力,而且需要联网,很不方…...

【MySQL进阶】SQL优化
😊😊作者简介😊😊 : 大家好,我是南瓜籽,一个在校大二学生,我将会持续分享Java相关知识。 🎉🎉个人主页🎉🎉 : 南瓜籽的主页…...

最新版海豚调度dolphinscheduler-3.1.3配置windows本地开发环境
0 说明 本文基于最新版海豚调度dolphinscheduler-3.1.3配置windows本地开发环境,并在windows本地进行调试和开发 1 准备 1.1 安装mysql 可以指定为windows本地mysql,也可以指定为其他环境mysql,若指定为其他环境mysql则可跳过此步。 我这…...
csv文件完整操作总结
csv文件完整操作总结 1.概述 csv 模块主要用于处理从电子数据表格Excel或数据库中导入到文本文件的数据,通常简称为 comma-separated value (CSV)格式因为逗号用于分离每条记录的各个字段。 2.读写操作 2.1.测试数据 创建一个test.csv文…...

时间序列预测--基于CNN的股价预测(Matlab代码实现)
目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨💻4 Matlab代码 💥1 概述 时间序列预测有很多方法,如传统的时序建模方法ARIMA、周期因子法、深度学习网络等,本次实验采用最简单的…...

Dubbo与Spring Cloud优缺点分析(文档学习个人理解)
文章目录核心部件1、总体框架1.1 Dubbo 核心部件如下1.2 Spring Cloud 总体架构2、微服务架构核心要素3、通讯协议3.1 Dubbo3.2 Spring Cloud3.3 性能比较4、服务依赖方式4.1 Dubbo4.2 Spring Cloud5、组件运行流程5.1 Dubbo5.2 Dubbo 运行组件5.3 Spring Cloud5.4 Spring Clou…...

单元测试工具——JUnit的使用
⭐️前言⭐️ 本篇文章主要介绍单元测试工具JUnit的使用。 🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁 🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言 🍉博客中涉及源码…...

Linux_基本权限
Linux入门第二篇已送达! Linux_基本权限shell外壳权限Linux的用户分类角色划分Linux的文件文件类型查看权限目录的权限默认权限粘滞位shell外壳 为了保护操作系统,用户的指令不能由操作系统直接进行执行,需要一个中间者,比如Linu…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...