首页 > 编程语言 >Mybatis 源码分析

Mybatis 源码分析

时间:2023-03-08 20:13:29浏览次数:45  
标签:分析 Mapper 调用 对象 执行 源码 Mybatis 解析 方法

转自:https://juejin.cn/post/6983853041686577189
mybatis是当今Java项目使用最为广泛的ORM框架,免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。本文将会带大家从源码角度分析mybatis核心工作流程,知其然,更知其所以然。

源码地址:

mybatis中文注释源码

mybatis使用示例

寻找入口

要想理解完整的工作流程,肯定要从mybatis原始API入手,下面是使用原始API使用mybatis的示例代码(MyBatisTest.java):

@Test
public void testSelect() throws IOException {
    String resource = "config/mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
    try {
        // 通过sqlSession先获取到Mapper接口对象
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        Blog blog = mapper.selectBlogById(1688);
        System.out.println(blog);
    } finally {
        session.close();
    }
}
复制代码

可以明显看出来,入口代码就是SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

配置解析

mybatis有两种配置文件,一种是全局配置文件mybatis-config.xml,另一种就是各个具体的Mapper文件xxxMapper.xml 。在上面的代码中,核心就是基于mybatis-config.xml配置文件构建出了sqlSessionFactory对象。跟进SqlSessionFactoryBuilderbuild()方法: image.png

上面代码的关键是调用了parserparse()方法,它会返回一个Configuration类。 image.png

明显可以看出来,解析配置是从configuration根节点开始解析的。具体解析过程如下: image.png

解析配置完整方法上所示,主要的解析过程包含属性、类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型处理器、映射器等等。下面挑选最关键的部分具体讲解。

属性 propertiesElement()

image.png

第一个是解析<properties>标签,读取我们引入的外部配置文件。解析的最终结果就是我们会把所有的配置信息放到名为defaultsProperties对象里面,最后把XPathParserConfigurationProperties属性都设置成填充后的Properties对象。

类型别名 typeAliasesElement()

image.png

类型别名是解析<typeAliases>标签,它有两种定义方式,一种是直接定义一个类的别名,另一种是指定一个包,此时这个package下面所有的类的名字就会成为这个类全路径的别名。类的别名和类的关系,放在一个TypeAliasRegistry对象里面。

插件 pluginElement()

image.png

插件是解析<plugins>标签。<plugins>标签里面只有<plugin>标签,<plugin>标签里面只有<property>标 签。标签解析完以后,会生成一个Interceptor对象,并且添加到ConfigurationInterceptorChain属性里面,它是一个List

对象工厂 objectFactoryElement()objectWrapperFactoryElement()

image.png

这两个标签是用来实例化对象用的,分别解析的是<objectFactory><objectWrapperFactory>这两个标签,对应生成ObjectFactoryObjectWrapperFactory对象,同样设置到Configuration的属性里面。

设置 settingsElement()

image.png

设置是解析<settings>标签,首先将所有子标签全部转换成了Properties对象,然后再将相应的属性设置到Configuration中。

二级标签里面有很多的配置,比如二级缓存,延迟加载,自动生成主键这些。需要注意的是,所有的默认值都是在这里赋值的。如果我们不知道这个属性的值是什么,也可以到这一步来确认一下。 所有的值,都会赋值到Configuration的属性中。

环境 environmentsElement()

image.png

一个environment就是对应一个数据源,所以在这里我们会根据配置的<transactionManager>创建一个事务工厂,根据<dataSource>标签创建一个数据源,最后把这两个对象设置成Environment对象的属性,放到 Configuration里面。

类型处理器 typeHandlerElement()

image.png

TypeAlias一样,TypeHandler有两种配置方式,一种是单独配置一个类,一种是指定一个package。最后我们得到的是JavaTypeJdbcType,以及用来做相互映射的TypeHandler,并将其保存在 TypeHandlerRegistry对象里面。

映射器 mapperElement()

image.png image.png

映射器支持4种配置方式,包括类路径、绝对url路径、Java类名和自动扫描包方式。下面以自动扫描包为例,具体说明。查看configuration.addMappers(mapperPackage)实现: image.png image.png

查找指定包下所有的mapperClass,并注册。继续跟进addMapper(mapperClass)实现: image.png

添加映射就是将Mapper类型和对应的MapperProxyFactory关联,放到一个Map容器中。并且在这里还会去解析Mapper接口方法上的注解。具体来说是创建了一个MapperAnnotationBuilder,我们点进去看一下 parse()方法。 image.png

parseCache()parseCacheRef()方法其实是对@CacheNamespace@CacheNamespaceRef这两个注解的处理。parseStatement()方法里面的各种getAnnotation(),都是对注解的解析,比如@Options@SelectKey@ResultMap等等。

最后同样会解析成MappedStatement对象,也就是说在XML中配置,和使用注解配置,最后起到一样的效果

小结

经过上述步骤,我们主要完成了config配置文件、Mapper文件、Mapper接口上的注解的解析。我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration的实例。

创建会话

在构建好SqlSessionFactory对象,接下来创建会话了,也就是执行SqlSession session = sqlSessionFactory.openSession(),获取会话对象。具体源码如下: image.png

首先从Configuration里面拿到Enviroment,再通过Enviroment获取事务工厂TransactionFactory。接下里,通过事务工厂来产生一个事务,再生成一个执行器(事务包含在执行器里),然后生成DefaultSqlSession

创建 Transaction

如果配置的是JDBC,则会使用Connection对象的commit()rollback()close()管理事务。

如果是Spring + MyBatis,则没有必要配置,因为会直接使用applicationContext.xml里面配置数据源和事务管理器,覆盖MyBatis的配置。

创建 Executor

image.png

Executor的基本类型有三种:SIMPLEBATCHREUSE,默认是SIMPLE (settingsElement()读取默认值),他们都继承了抽象类BaseExecutor

Executor三种类型的区别是什么?

  1. SimpleExecutor:每执行一次updateselect,就开启一个Statement对象,用完立刻关闭 Statement对象。
  2. ReuseExecutor:执行updateselect,以 sql 作为key查找Statement对象,存在就使用,不存在就创建。用完后,不关闭Statement对象,而是放置于Map内, 供下一次使用。简言之,就是重复使用 Statement对象
  3. BatchExecutor:执行update(没有select,JDBC 批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个 Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与 JDBC 批处理相同。

如果配置了cacheEnabled=ture,会用装饰器模式对executor进行包装:new CachingExecutor(executor)

最后调用Executor的插件,执行对应的插件逻辑。(插件原理后续再讲)

小结

创建会话的过程,主要是获得了一个DefaultSqlSession,里面包含了一个Executor,它是SQL的执行者

获取Mapper对象

在创建好SqlSession对象之后,接下来就是获取Mapper对象了。Mapper.xml文件与Mapper类型通过namespace进行关联,Statement ID与方法名进行了关联。因此,调用Mapper的方法就能执行相应的SQL了。

namespace等于类的全路径名,Statement ID等于方法名。

image.png image.png image.png

最后调用MapperRegistrygetMapper()方法。在前面配置解析阶段,我们讲过添加映射就是将Mapper类型和对应的MapperProxyFactory关联,放到一个Map容器中。而这里就是根据Mapper类型,得到对应的MapperProxyFactory,接下来通过代理工厂就可以创建一个Mapper代理对象了。 image.png

执行SQL

由于所有的Mapper都是MapperProxy代理对象,所以任意的方法都是执行MapperProxyinvoke()方法。 image.png

invoke()方法的执行步骤主要有2步,第一步是查找MapperMethod,第二步是执行方法

查找MapperMethod

image.png

优先从缓存中获取MapperMethod,缓存中没有则创建一个。

执行方法

执行方法就是调用MapperMethodexecute()方法。 image.png

可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法。调用 convertArgsToSqlCommandParam()将参数转换为SQL的参数。

接下来,我们以查询单行记录为例,最终会执行DefaultSqlSession.selectOne()方法。 image.png

可以看到,selectOne()最终也是调用了selectList()image.png

SelectList()中,我们先根据Statement IDConfiguration中拿到 MappedStatement,这个ms上面有我们在 xml中配置的所有属性,包括idstatementTypesqlSourceuseCache、入参、出参等等。

查询

在获取到MappedStatement之后,接下就是调用执行器的query()方法了。查询是最复杂的sql处理,接下来详细分析查询的执行流程。

前面我们说到了Executor有三种基本类型,SIMPLE/REUSE/BATCH。另外还有一种包装类型CachingExecutorimage.png

如果启用了二级缓存,就会先调用CachingExecutorquery()方法,里面有缓存相关的操作,然后才是再调用基本类型的执行器,比如默认的SimpleExecutor。最终会调用BaseExecutor.query()方法: image.png

主要包含获取绑定sql、创建CacheKey和执行查询。

获取绑定sql

image.png

根据输入参数,获取绑定sql

创建CacheKey

image.png

MyBatis 对于其 Key 的生成采取规则为:[mappedStementId + offset + limit + SQL + queryParams + environment]生成一个哈希码

执行查询

image.png

优先从缓存中查询,如果没有,则从数据库查询:queryFromDatabase()

数据库查询

image.png

先向缓存用占位符占位。执行查询后,移除占位符,放入数据。执行查询调用的是doQuery()方法。

执行查询 doQuery()

image.png

主要包含创建StatementHandler、准备语句和查询三步。

创建StatementHandler

image.png image.png

configuration.newStatementHandler()中,创建了一个 StatementHandler,先得到 RoutingStatementHandlerRoutingStatementHandler里面没有任何的实现,它是用来创建基本的 StatementHandler的。这里会根据MappedStatement里面的statementType决定StatementHandler的类型 。 默认是PREPARED。 接下来以预处理语句处理器(PREPARED)为例进行分析。 image.png

这里直接调用了父类BaseStatementHandler的构造方法。 image.png

在构造方法里面,重点是生成了处理参数的ParameterHandler和处理结果集的ResultSetHandler。它们都可以被插件拦截。所以在创建之后都要用拦截器包装。 image.png

准备语句

image.png 调用prepareStatement()方法对语句进行预处理。主要是根据连接准备语句和参数化处理。

查询

RoutingStatementHandlerquery()方法最终委派给PreparedStatementHandler执行。 image.png

在这里,调用了PreparedStatementexecute()方法,它的底层就是是调用了JDBC的PreparedStatement进行执行,这里就不展开讲了。我们的重点是结果集的处理。 image.png

首先我们会先拿到第一个结果集,如果没有配置一个查询返回多个结果集的情况,一般只有一个结果集。也就是下面的这个while循环只会执行一次,然后会调用handleResultSet()方法。 image.png

如果没有配置结果处理器,则会使用默认的结果处理器DefaultResultHandler,否则使用配置的结果处理器进行处理。

小结

总之,调用Mapper对象的方法就是执行sql。首先,它会根据方法和参数得到绑定的sql,然后创建语句处理器、准备好语句等等,之后就会通过JDBC执行sql,最后处理结果集,将结果转化为Mapper方法的返回类型返回。

总结

总的来说,mybatis核心工作流程是非常清晰的。

  1. 第一步是配置解析,这一步的重点就是根据配置文件生成Configuration,基于它然后构建DefaultSqlSessionFactory对象。
  2. 第二步是创建会话,主要是获得了一个DefaultSqlSession对象,里面包含了一个Executor,它是SQL的执行者。
  3. 第三步是获取Mapper对象,这一步主要是根据第一步注册号的Mapper,创建好MapperProxyFactory代理对象。后续所有的方法调用都会调用MapperProxyFactoryinvoke()方法。
  4. 第四步是执行方法,在这里会创建语句处理器、准备好语句等等,之后就会通过JDBC执行sql,最后处理结果集,将结果转化为Mapper方法的返回类型返回。

标签:分析,Mapper,调用,对象,执行,源码,Mybatis,解析,方法
From: https://www.cnblogs.com/luojw/p/17195927.html

相关文章

  • vuex-router-sync 源码解析
    vuex-router-sync:路由状态管理,保持vue-router和vuex存储同步。import{sync}from'vuex-router-sync'importrouterfrom'@/router'importstorefrom'@/store'syn......
  • mybatis种的ResultMap嵌套
    mybatis中的返回类嵌套一个list,如何实现?<resultMapid="CusMap"type="com.yang.webstarter.entity.SysUser"><collectionproperty="books"javaType="java......
  • Tomcat 中的 NIO 源码分析
    转自:https://javadoop.com/post/tomcat-nio之前写了两篇关于NIO的文章,第一篇介绍了NIO的Channel、Buffer、Selector使用,第二篇介绍了非阻塞IO和异步IO,并展示了简......
  • java springboot mybatis plus 3.4 实现执行任意 sql 语句
    试了SqlRunner一直失败,不知道原因,于是试了如下方法,完美解决。@AutowiredprivateSqlSessionFactorysqlSessionFactory;publicList<Map<String,Object>>exec......
  • 地理信息分析_不同城市
    关注你去过的城市城市如何及为什么成为目前的样子,重点观察有机模式、网络、图形式城市、壮丽风格以及城市天际线等一些主题,从中解释城市模式蕴藏的秩序隐藏在地名下......
  • 深入理解需求分析的目标(C系架构设计法)
    需求分析的目标:是尽可能准确、全面、深入的理解业务。1:理解“尽可能准确”首先,需求分析,要做的事,肯定是去理解业务,但是要达到什么样的程度,才算是我们理解了这个业务呢?第......
  • jmeter性能测试实例3解析--性能瓶颈分析过程
    场景要求1、用户登陆---每个用户登陆一次(仅一次控制器)2、压测试接口获取门店列表性能场景指标1、验证最大在线用户数--(负载测试)2、错误率<0.5%3、请求响应时间<2......
  • 8、Redis的RDB工具分析key的大小
    1.什么是Redis的大key?(BigKey)redis存储数据的时候,当某个key的值比较大(包括字符串、列表等数据类型),key的数据越大,占用的内存和空间就越多。BigKey(大key)Bigkey指的是redis......
  • 航空公司客户价值分析
    #7.1数据探索importmatplotlib.pyplotaspltimportpandasaspddatafile=r"C:\Users\admin\Desktop\39984b862fb97f6326368ad28c7b6a4e_a8b8b4ac463b459805927f22a40e65......
  • 分布式数据库代理导出分片大表僵死或卡死原因分析及调优
    1、背景现象****分布式数据库导出分片大表代理卡死或者代理僵死2、 ****分布式数据库导出小表或者不是分片表时发现数据可以导出,但是当数据量大时就没法导出数据,再复现一......