您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

时间:2021-06-01 10:15:14  来源:今日头条  作者:胖哥叨一叨

啃了一周tkMyBatis源码,51张图,5个主要流程,构成了tkmybatis的源码组成。tkmybatis包括了mybatis的部分,源码相比于mybatis多了个mApperscan的注解处理,其余部分是一致的。理清了tkmybatis,就理清了mybatis源码,同时对mybatis的机制能有更深刻的认识。

纯mybatis每个持久化操作都要写sql,会显得有些繁琐。现在市面上也有很多的插件,比如mybatis逆向工程,mybatisCodeHelperPro等,可以在xml文件中生成一些常用的sql和对应的mapper接口方法。也有一些mybatis的第三方工具框架,帮我们免去单表操作的sql编写,比如tkmybatis。tkmybatis源码版本:

<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>

一、tkmybatis使用案例

mybatis系列-5分钟教你提升CRUD开发效率200%

二、整体流程

tkmybatis流程大概分为以下几步:

  • 根据properties文件中配置的xml位置,为每个mapper接口生成一个MappedStatement对象(此时对象的SQLSource为不可执行的Provider)
  • 根据ScanMapper接口, 将接口加入到Spring 容器中,创建对应的BeanDefinition,然后修改BeanDefinition中的Bean类型为MapperFactoryBean
  • Spring容器实例化MapperFactoryBean实例,在初始化方法中,获取第一步每个接口生成的MappedStatement对象,并将MappedStatement对象的SQLSource修改为可执行的SqlSource(Provider生成xml格式的SQL,根据该SQL利用languageDriver创建新的sqlSource)
  • 将mapper的bean实例注入到需要依赖该实例的service中,此时会Spring容器调用MapperFactoryBean的getObject方法,该方法创建了一个mapper的代理类(使用jdk的动态代理方法),并注入到service里面去
  • 执行SQL时,对mapper的SQL操作,都由mapper的代理类mapperProxy来实现,实现过程跟原生mybatis用sqlsession创建的代理类一样的,只是这里没有显示的使用sqlsession来创建代理类,而是放到了MapperFactoryBean的getObject里面,将生成的代理类注入给service

三、源码解析:

1、Mapper接口扫描:

a、接口扫描入口位置

入口@MapperScan

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

这个注解会@Import进来一个tk.mapper的扫描器(将MapperScannerRegistrar导入到到Spring容器中,并将其声明成一个bean,该类的功能是处理注解MapperScan,具体过程见后面)

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

MapperScannerRegistrar实现了spring的
ImportBeanDefinitionRegistrar接口, 并实现了registerBeanDefinitions方法。【注:如下图Spring容器在初始化的时候,会先扫描基本注解如controller等,然后扫描第三方jar包中的组件,扫描完成后再找到实现了
ImportBeanDefinitionRegistrar接口的Bean,并将当前的AnnotationMetadata和BeanDefinitionRegistry作为参数传入,通过该Bean扫描自定义注解的组件进来】

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

该bean在实例化的时候,会调用registerBeanDefinitions方法来扫描并导入mapper接口,接着来看下扫描过程。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

b、创建扫描器并利用MapperScan参数初始化该扫描器

该步骤创建一个扫描器, 利用MapperScan参数初始化该扫描器,各参数意义后面说,默认为空也没关系。同时还会根据MapperScan参数确定扫描package的范围。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 


亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

c、注册扫描器filter

之所以把这个单独提出来,是因为这一步决定了scanner能扫描出哪些mapper,符合filter条件的就会被扫描出来。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

看下registerFilters的具体实现:

在registerFilters中,扫描器会添加includeFilter和excludeFilter,如果扫描出来的类在某一个excludeFilter中,则放弃该类,如果扫描出来的类在某一个includeFilter中,则保存该类到扫描的返回结果中,excludeFilter要先于includeFilter进行判断。详见(
ClassPathScanningCandidateComponentProvider的isCandidateComponent方法)。

下图中annotationClass和markerInterface都是在前面初始化扫描器的时候,通过MapperScan注解传入的。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 


亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

d、调用doScan方法扫描mapper

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

ClassPathMapperScanner这个扫描器继承了
ClassPathBeanDefinitionScanner接口,并重写了doScan方法和isCandidateComponent

doScan:里面会直接复用父类自带的doScan方法,因为这就是spring扫描包中的bean的方法,在该方法中,会先使用上一步调动registerFilters方法注册的过滤器判断扫描出来的类是否符合条件(excludefilter和includefilter判断),然后再利用重写后的isCandidateComponent方法进一步判断是否符合条件(默认接口类是不符合Spring扫描条件的,这里通过重写该方法,判断扫描出来的类是不是接口且该类metadata中的encloseclass值为null,是的话就符合扫描条件),两个判断都通过,则认为是扫描出来的mapper接口类。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

重写的isCandidateComponent方法

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

调用父类的doScan后,会扫描到basePackage指定包下面的mapper接口,并封装成BeanDefinitionHolder的集合。BeanDefinitionHolder包含了BeanDefinition,同时包括BeanDefinition的名称和别名

e、使用processBeanDefinitions对扫描出来的mapper的BeanDefinition进行一些修改

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

processBeanDefinitions主要做了一下几个处理:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 


亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

上图处理中,最重要的就两个,一是更改BeanClass, 二是设置autowired-mode = by type,使得SqlSeesionTemplate可以作为MapperFactoryBean的属性注入进来(autowired-mode常见的有三种, AUTOWIRE_NO、AUTOWIRE_BY_NAME、AUTOWIRE_BY_TYPE,是基于xml的Spring配置时,用来定义Bean的注入方式的。利用@Component等注解创建的Bean默认值都是AUTOWIRE_NO,表示无需进行属性的注入,有@autowire等注解时按注解的方式完成属性注入,AUTOWIRE_BY_NAME和AUTOWIRE_BY_TYPE分别表示按名称和按类型进行参数的注入。如果该属性为这两个值,Spring容器在创建Bean的时候会对Bean的属性自动完成注入,注入时会扫描set方法,调用set方法并对set方法的参数注入,从而实现属性的注入。注意,这里因为是扫描出来的Mapper类的Bean对象且没有@Component等注解,所以就通过修改autowired-mode的方式,通知Spring容器,对该Bean属性进行注入)。

好了,扫描Mapper的工作到此为止,接下来就是Mapper接口的实例化了。

2、Mapper 接口Bean的实例化:

上面讲到Mapper接口的BeanDefinition的BeanClass被改成了tk.mybatis中的MapperFactoryBean。那么实例化的工作主要会由这个类(实例化出另外一个Bean来替代原始的Mapper接口的Bean)来完成。下面是这个类的依赖关系图。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

在图中,MapperFactoryBean集成的DaoSupport类实现了InitializingBean接口,那么spring在完成属性注入后,会调DaoSupport的afterPropertiesSet方法。在该afterPropertiesSet中调用了checkDaoConfig方法,由于MapperFactoryBean重写了checkDaoConfig方法,所以在Bean属性注入完成后,会调用MapperFactoryBean的checkDaoConfig方法。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 


亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

a、关键属性注入

下面来看看MapperFactoryBean的checkDaoConfig方法都做了些什么。但是看之前,我们先简单了解下该Bean中注入的主要属性,这些属性在checkDaoConfig方法中用到了,如果不讲清楚的话,会很疑惑这些属性是从哪里来的,了解了才能对tk-mybatis与Spring的集成更加的清楚。

mapperInterface的注入:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

SqlSession的注入

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

那么被注入的SqlSessionFactory和SqlSessionTemplate是如何被创建的呢,看下图,玄机就在
mybatis-spring-boot-starter的依赖包中的mybatis-spring-boot-autoconfigure:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

查看该jar包
mapper-spring-boot-autoconfigure-2.1.5.jar,可以发现在META-INF下有个spring.factories文件。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

该配置利用Spring的组件扫描机制,配置了Spring加载jar包后需要扫描的jar包内的配置类。该机制可以参考《004-JAVA基础-02-Spring类SPI机制,如何将jar包中的类加载到BeanFactory中进行管理》。文件内容如下:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

我们看下这个类,这个类被Spring加载进来后会做很多事情:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

创建SqlSessionFactory的Bean:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

创建SqlSessionTemplate的Bean:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

到这里,MapperFactoryBean的关键属性的注入讲完了。

b、SqlSessionFactory初始化操作内容

前面讲到MapperFactoryBean自动注入了SqlSessionFactory的Bean,为了讲清楚MapperFactoryBean在属性注入后执行的checkDaoConfig操作,我们先了解下SqlSessionFactory初始化的时候都做了什么事情。tkmybatis中SqlSessionFactory的初始化基本就是调用mybatis的初始化过程。简要概括为以下几点:

  • 读取Properties配置文件,提取mybatis相关配置
  • 解析xml文件,将xml文件mapper节点中的mapper类解析并存放到mapperRegistr中,作为knowsmapper
  • 根据mapper的接口方法,为每一个方法创建一个对应的mappedStatement(敲黑板,checkDaoConfig操作主要就是为了调整它),创建mappedStatement时会解析mapper中的method方法,创建对应的SqlSource(根据注解类型判断,如果是insert等基本注解,则利用LanguageDriver和注解上的SQL语句来创建SqlSource,如果是Provider等注解,tkMybatis就是这一类,则创建的SqlSource为ProviderSqlSource)

下面从源码里面看下这几部分:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

factory.getObject()方法跟下去,发现接着会用加载配置的xml文件,生成Resource列表,然后遍历处理列表中的Resource

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

xmlMapperBuilder.parse()方法跟下去,发现是调用了Mybatis中Configuration的addMapper方法(实现中又是直接调用的MapperRegistry的addMapper方法),处理每一个mapper类,将其添加到mapper注册中心。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 


亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

再进一步,看addMapper方法是如何处理mapper类,注意这里knownMappers.put方法很重要,knowMappers中保存了根据xml配置获取到的所有的mapper类,这里在put的时候,将mapper类转换成了对应的代理类工厂MapperProxyFactory(MapperFactoryBean在完成初始化后,如果在其他类中需要注入MapperFactoryBean的实例,会由Spring容器调用它的getObject方法创建并注入进去。在getObject方法里会调用代理工厂类的实例方法,会利用jdk的动态代理技术为mapper类实例创建对应的代理类实例,返回给MapperProxyFactory的getObject方法,随后MapperProxyFactory将该代理类实例作为mapper类的实例(比如UserMapper的实例userMapper,实际就是Mapper的代理类实例),执行sql的时候,实际SQL调用都是在MapperProxy的invoke方法中进行处理的)

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 


亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

在parse()方法中,做如下操作:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

parseStatement方法完成MappedStatement创建,包括根据方法注解创建SqlSource,然后利用SqlSource创建MappedStatement。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 


亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

getSqlSource方法会根据mapper方法上的注解类型,决定返回何种SqlSource。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

到这里mapper解析的操作就完成了,MapperFactoryBean中的configuration保存了每一个mapper方法的mappedStatement以及已知的mapper类。

c、为每个方法对应的MappedStatement更新SqlSource

我们再回到MapperFactoryBean类,来看看它的checkDaoConfig方法都做了些什么。先把结论说了吧,checkDaoConfig会根据mapper接口将每个接口方法对应的sqlSource,由前面说的ProviderSqlSource转换成languageDriver创建的sqlSource(可以理解为tkMybatis根据每个方法的定义,利用Provider为每个方法在对应的mapper.xml中添加了方法对应的SQL语句,然后利用修改后的xml创建新的sqlSource,更新到MappedStatement里面去,只有更新后的MappedStatement,才能执行Mybatis的SQL处理。当然实际上是没有直接修改xml文件这一步的,只是打个比方,可以看后面的代码实现)。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

再继续看processConfiguration方法做了什么操作。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

再继续看processMappedStatement对MappedStatement的处理,该处理中会对MappedStatement中的SqlSource进行替换。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

看一下替换前,MappedStatement中的SqlSource如下图所示,是个Provider实现类。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

执行setSqlSource后,在下图中可以看见,MappedStatement中的sqlSource参数已经变成了DynamicSqlSource。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

接下来,我们继续看下setSqlSource到底是怎么改变sqlSource的。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

继续看mapperTemplate的setSqlSource方法,该方法完成了xml形式sql的创建和对应sqlSource的创建。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

跟进method.invoke方法,可以发现,该方法实际是在mapper接口方法对应的Provider实现类中执行的,如下图selectByExample方法由ExampleProvider来生成xmlsql。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

至此,tkMybatis就在checkDaoConfig方法中将接口方法对应的MappedStatement中的sqlSource由不能执行的Provider实现类,转换成了Mybatis中的可执行的sqlSource(根据xmlSql生成,和mybatis扫描mapper.xml文件中创建的MapperStatement和SqlSource具有同样的功效!)

到目前为止,经过1、Mapper接口扫描和2、Mapper 接口Bean的实例化就完成了tkMybatis的初始化工作。

3、将Mapper实例注入到依赖该实例的bean中

前面tkMybatis做了两件事情,一是扫描Mapper接口比如UserMapper,创建对应的Bean定义,并将其中的类型修改为MapperFactoryBean;二是实例化该Bean,因为类型已经修改为MapperFactoryBean,就完成MapperFactoryBean的实例化和初始化。

接下来还有关键的一步,怎么用这个Bean,我们在写service的时候,会通过@autowired注解将Mapper注入到service中,比如下面的:

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

以UsersMapper举个例子,Spring在注入UsersMapper时,会调用对应MapperFactoryBean实例的getObject方法,如下图,在该方法中,会创建UsersMapper的的代理类,进入getmapper这个方法,会跟到前面第2部分knowMappers部分,getmapper是从knowMappers获取的Mapper,该Mapper是一个Mapper接口的代理类MapperProxy,对UserMapper的方法调用都会有MapperProxy的invoke方法来实现。

这也就解决了“明明是Mapper的接口比如UserMapper,但是在bean定义中把它的类型改成了MapperFactoryBean,MapperFactoryBean又没有实现Mapper的接口方法,那么service中注入的mapper调用方法的时候是如何调用的问题”。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

4、实际SQL语句的执行

接着上一部分,调试下实际执行SQL语句的过程。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 


亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

其实,像下图一样,使用经过注入后的Mapper代理类,就跟原生Mybatis创建的代理类是一样的,执行的SQL操作的过程也是一样的。里面的每个接口方法也都有对应的MappedStatement实现。如果对invoke代理的过程感兴趣,可以调试跟进去看,看看如何invoke中如何用MappedStatement做具体的SQL处理,这里就不具体说了。

亲自啃了一周,终于把Mybatis源码理清,以后简历请写精通二字

 

总结:

和mybatis的关系:

  1. 在不影响mybatis原有功能的情况下,很好的拓展了mybatis的功能
  2. 扫描xml的工作依旧由mybatis来完成,再次扫描并注册mapper接口的功能以拓展的方式由tk.mapper复写,扫描后的结果依旧存放在mybatis的Configuration中,和mybatis自己扫描mapper接口的代码逻辑几乎一致,唯一添加的功能就是,对mybatis的Configuration中的由自己拓展的方法对应的MapperStatement的sqlSource进行更改,以此来提供具体可执行sql
  3. 后续执行持久化方法,依然是mybatis的代码功能,tk.mapper仅在扫描mapper接口阶段提供了SqlSource


Tags:Mybatis源码   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
本篇文章主要介绍了使用MyBatis框架完成数据库的增、删、改、查操作。准备工作运行schema.sql和data.sql脚本文件中的 SQL 语句创建t_user表并添加部分测试数据。schema.sql...【详细内容】
2022-07-15  Tags: Mybatis源码  点击:(0)  评论:(0)  加入收藏
简介MetaObject 是 MyBatis 中的反射工具类,用于获取和设置对象的属性值。示例List<Order> orders = new ArrayList<>(2);orders.add(new Order("1", "001", "美的电压力锅")...【详细内容】
2022-07-06  Tags: Mybatis源码  点击:(18)  评论:(0)  加入收藏
简介SqlRunner,一个非常实用的、用于操作数据库的工具类。该类对JDBC进行了很好的封装,结合SQL工具类,能够很方便地通过Java代码执行SQL语句并检索SQL执行结果。SqlRunner提供...【详细内容】
2022-07-05  Tags: Mybatis源码  点击:(19)  评论:(0)  加入收藏
1.ExecutorExecutor 是 MyBatis 的核心接口之一,其中定义了数据库操作的基本方法。在实际应用中经常涉及的 SqISession 接口的功能,都是基于 Executor 接口实现的。 BaseExec...【详细内容】
2022-01-13  Tags: Mybatis源码  点击:(96)  评论:(0)  加入收藏
啃了一周tkmybatis源码,51张图,5个主要流程,构成了tkmybatis的源码组成。tkmybatis包括了mybatis的部分,源码相比于mybatis多了个mapperscan的注解处理,其余部分是一致的。理清了...【详细内容】
2021-06-01  Tags: Mybatis源码  点击:(311)  评论:(0)  加入收藏
▌简易百科推荐
本篇文章主要介绍了使用MyBatis框架完成数据库的增、删、改、查操作。准备工作运行schema.sql和data.sql脚本文件中的 SQL 语句创建t_user表并添加部分测试数据。schema.sql...【详细内容】
2022-07-15  嗨皮汪小成    Tags:MyBatis   点击:(0)  评论:(0)  加入收藏
1 Hive基本概念Hive是一个构建在Hadoop上的数据仓库框架。最初,Hive是由Facebook开发,后来移交由Apache软件基金会开发,并作为一个Apache开源项目。Hive是基于Hadoop的一个数据...【详细内容】
2022-07-15  秃头Java人    Tags:Hive   点击:(2)  评论:(0)  加入收藏
今天给大家讲讲 SpringBoot 框架 整合 Elasticsearch 实现海量级数据搜索。一、简介在上篇ElasticSearch 文章中,我们详细的介绍了 ElasticSearch 的各种 api 使用。实际的项...【详细内容】
2022-07-15  java小悠    Tags: Elasticsearch   点击:(3)  评论:(0)  加入收藏
SpringBoot开发Restful接口,有什么API规范吗?如何快速生成API文档呢?Swagger 是一个用于生成、描述和调用 RESTful 接口的 Web 服务。通俗的来讲,Swagger 就是将项目中所有(想要...【详细内容】
2022-07-14  Java全栈知识体系    Tags:Swagger   点击:(2)  评论:(0)  加入收藏
一、部署准备安装数据库、jdk、nginx、域名证书1、下载 nginx,官方网址如下:http://nginx.org/en/download.html2、解压安装包到任意目录 如:G:\nginx二、前端部署1、打开前端...【详细内容】
2022-07-14  智慧魔法豆浆    Tags:vue   点击:(2)  评论:(0)  加入收藏
SpringBoot 内置支持的 Web 容器有 Tomcat、Undertow、Jetty 和 Netty。默认情况下,这些 Web 服务的 AccessLog 日志是不开启的,而 AccessLog 日志对于做接口统计尤为重要。如...【详细内容】
2022-07-13  BUG弄潮儿    Tags:AccessLog 日志   点击:(10)  评论:(0)  加入收藏
什么是Starterstarter 是springboot 的核心,每个starter负责实现特定的功能,使用者只需引入starter即可自动配置,无需关心框架整合带来的问题。Starter 项目结构src |- main...【详细内容】
2022-07-12  IT食者    Tags:SpringBoot   点击:(9)  评论:(0)  加入收藏
mybaits非必填项处理数据库表字段khzjyxqx为日期型,非必填, 前台页面如下: 后台mybaits处理如下: 如果不处理,当为空时khzjyxqx=&#39;&#39;时会报错。<update id="updatesave" pa...【详细内容】
2022-07-11  在水一方357159258    Tags:mybaits   点击:(10)  评论:(0)  加入收藏
关于过气网红编程语言 Ruby,我们此前曾发过一篇文章去回顾其大受追捧的过往,并讨论了它每况愈下的生存状态。不过人气并不能直接说明语言质量差,一方面 Ruby on Rails(用 Ruby...【详细内容】
2022-07-08  InfoQ    Tags: Web 框架   点击:(9)  评论:(0)  加入收藏
1、JWT的构成- 头部(header):描述该JWT的最基本的信息,如类型以及签名所用的算法。- 负载(payload):存放有效信息的地方。- 签证(signature):base64加密后的header、base64加...【详细内容】
2022-07-08  dream19    Tags:SpringBoot   点击:(10)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条