【Mybatis源码解析】mapper实例化及执行流程源码分析

news2024/11/16 6:30:09

文章目录

    • 简介
    • 环境搭建
    • 源码解析

基础环境: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.Driver
    url: jdbc:mysql://issavior-aliyun-rds.mysql.rds.aliyuncs.com:3306/test?useUnicode=true&charsetEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    username: root
    password: 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的时候加载:

在这里插入图片描述
在这里插入图片描述

分析一下配置类的注解:

  1. @org.springframework.context.annotation.Configuration
    配置类
  2. @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
    @ConditionalOnClass注解的作用是当项目中存在某个类时才会使标有该注解的类或方法生效,底层是通过Class.forName()判断是否存在该类。
  3. @ConditionalOnSingleCandidate(DataSource.class)
    @ConditionalOnSingleCandidate表示当指定Bean只有一个,或者虽然有多个但是指定首选Bean,这时候才会将其放到容器中。
  4. @EnableConfigurationProperties(MybatisProperties.class)
    将properties和yml配置文件属性转化为bean对象使用。
  5. @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方法处理结果并返回。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

哈希表题目:矩阵置零

文章目录题目标题和出处难度题目描述要求示例数据范围进阶解法一思路和算法代码复杂度分析解法二思路和算法代码复杂度分析解法三思路和算法代码复杂度分析题目 标题和出处 标题&#xff1a;矩阵置零 出处&#xff1a;73. 矩阵置零 难度 3 级 题目描述 要求 给定一个 m…

oracle单库重建undo表空间步骤

前言&#xff1a;undo表空间不足可直接增加空间&#xff1b; alter tablespace UNDOTBS1 add datafile /data/oradata/datafile/UNDOTBS102.dbf size 30g autoextend off; 注&#xff1a;单次增加最大不得超过32G 回收空间 缩小表空间直接可以resize命令缩小&#xff1b…

webpack(高级)--创建自己的loader 同步loader 异步loader loader参数校验

webpack 创建自己的loader loader是用于对模块的源代码进行转换&#xff08;处理&#xff09; 我们使用过很多loader 比如css-loader style-loader babel-loader 我么如果想要自己创建一个loader 首先创建webpack环境 pnpm add webpack webpack-cli -D 之后创建loader模块…

Hadoop初步理解

产生原因 在之前&#xff0c;数据量小&#xff0c;增长速度慢&#xff0c;且数据基本都是文件。储存和处理这些数据并不麻烦&#xff0c;单个存储单元和处理器组合就可以。 之后随着互联网发展&#xff0c;产生了大量多种形式的数据。 非结构化数据&#xff1a;邮件、图像、音…

盘点3个.Net开发的WMS仓库管理系统

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 仓库管理系统在企业中&#xff0c;重要性越来越高&#xff0c;不仅可以提高效率&#xff0c;还能降低企业的压力&#xff0c;企业通过协调和优化资源使用和物料流动&#xff0c;能极大程度地提升了管理效率&…

中国500强|长虹控股集团携手契约锁,推动采购-人事业务电子签

四川长虹电子控股集团&#xff08;以下简称“长虹控股集团”&#xff09;是国内知名的电器制造商之一&#xff0c;拥有六家上市公司、一家新三板的公众公司&#xff0c;入选世界品牌500强、中国企业家协会发布的中国500强企业榜单。此次&#xff0c;长虹控股集团携手契约锁打造…

数据分析与SAS学习笔记3

SAS在最新的展示图&#xff0c;表现力比较丰富。 SAS的处理流程&#xff1a; 数据步 过程步&#xff1a; ETL是数据分析非常重要的步骤。70%-90%花在收集数据以及整理数据&#xff0c;数据分析数据的时间不是很多的。 一个完整的数据步和过程步&#xff1a; 数据步基本语句总…

新手学习node.js基础,node.js安装过程,node.js运行环境及javascript运行环境.

学习node.js1.什么是node.js?2.node.js中的javaScript运行环境3.node.js可以做什么&#xff1f;4. node.js学习思路5.node.js环境的安装6.如何在node.js中执行JavaScript代码1.什么是node.js? node.js是一个基于Chrome v8 引擎的JavaScript运行环境(后端) node.js官网 &…

Flutter for Android

将 Flutter 添加到现有应用程序 在 Flutter 中一次重写整个应用程序是不切实际的。 对于这些情况&#xff0c;Flutter 可以作为库或模块逐步集成到您现有的应用程序中。 然后可以将该模块导入到您的 Android 或 iOS&#xff08;当前支持的平台&#xff09;应用程序中&#xff…

GEE学习笔记 八十六:分类中的特征重要性分析

之前在GEE中做随机森林分类时候&#xff0c;很多人都在问如何做特征重要性分析&#xff1f;但是在GEE之前并没有相关API可以做特征重要性分析&#xff0c;最新的API更新后GEE也可以做特征重要性分析了。 1、目前常用的包含特征重要信息分析的分类方法包括&#xff1a; &#…

基础篇:03-SpringCloud工程部署启动

目录 1.工程搭建部署 方案一&#xff1a;完整工程导入 方案二&#xff1a;从零开始搭建 1.工程与module创建 2.数据库导入 3.项目启动 3.1 启动并访问user-service 3.2 启动并访问order-service 4.服务远程调用 时序图说明 服务远程调用实现 注入RestTemplate Res…

自学web前端觉得好难,可能你遇到了这些困境

好多人跟我说上学的时候也学过前端&#xff0c;毕业了想从事web前端开发的工作&#xff0c;但自学起来好难&#xff0c;快要放弃了&#xff0c;所以我总结了一些大家遇到的困境&#xff0c;希望对你会有所帮助。 目录 1. 意志是否坚定 2. 没有找到合适自己的老师 3. 为了找…

论文阅读【PAMI_2022】FSGANv2: Improved Subject Agnostic Face Swapping and Reenactment

论文阅读【PAMI_2022】FSGANv2: Improved Subject Agnostic Face Swapping and Reenactment论文的缩写全拼一、摘要&#xff08;问题&#xff0c;贡献&#xff0c;效果&#xff09;二、引言&#xff08;idea&#xff09;三、方法(FSGAN)1.Detection and tracking2.Generator ar…

node学习-3:服务器渲染和客户端渲染

1. 概念 一.服务端渲染&#xff0c;后端嵌套模板&#xff0c;后端渲染模板&#xff0c;SSR&#xff08;后端把页面组装好&#xff09; 做好静态页面&#xff0c;动态效果 把前端代码提供给后端&#xff0c;后端则把静态html以及里面的假数据给删除掉 通过模板进行动态生成h…

8个让你收入翻倍的高质量免费网站

毕业几年了&#xff0c;如果你的月薪不到1w&#xff0c;还是做着重复机械的动作&#xff0c;现在马上往下看&#xff0c;今天分享6个资源网站让你的收入暴增&#xff0c;尤其是最后一个。每天花一个小时&#xff0c;让你工资翻倍&#xff0c;从此在职场横着走&#xff0c;再也不…

GEE学习笔记 八十三:【GEE之Python版教程十三】几何图形

遥感分析中用到的数据主要就是这两大类&#xff1a;矢量数据和栅格数据。在Google Earth Eninge中&#xff0c;它为我们讲这两类数据封装成为了以下几类数据。 下面几节内容我会依次讲解相关内容的详细信息&#xff0c;这一节先讲一下几何图形ee.geometry。 学习任何新的东西首…

设计模式之抽象工厂模式(C++)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、抽象工厂模式是什么&#xff1f; 抽象工厂模式是一种创建型的软件设计模式&#xff0c;该模式相当于升级版的工厂模式。 如果…

采集知乎评论

声明 本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! excel保存效果图: 首先我们找一个评论比较多的帖子,如下图所示有874条评论 点击评论…

算法刷刷刷| 回溯篇| 组合问题大集合

77.组合 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4], [3,4], [2,3], [1,2], [1,3], [1,4],] import java.util.ArrayList; import java.util.List;clas…

可靠、稳定、安全,龙蜥云原生容器镜像正式发布!

文/云原生 SIG01背景随着云原生的蓬勃发展&#xff0c;越来越多的企业在自己的生产或者测试环境使用云原生技术&#xff0c;而容器镜像正是云原生技术中应用的实际运行环境。一个好的容器运行环境即容器镜像会真正关系到应用的体验、演进和维护。那么选择一个好的容器镜像需要考…