Mybatis基础-执行流程解析

前言

在上篇文章中,向大家介绍了Mybatis配置文件的解析过程,分析了框架内部是怎么一步步将文件映射为java对象的,本章我们讲解在调用一个接口时框架内部是如何拿到我们的具体sql,又是如何将结果封装为我们需要的类型,最终展现在我们面前的。

Session的获取

从上篇文章我们知道,解析完成后会生成一个SqlSessionFactory对象,而我们所有的CRUD都需要通过Session会话来进行操作,因此我们需要通过该工厂对象生成一个SqlSession对象来执行后续步骤。SqlSessionFactory的默认实现为DefaultSqlSessionFactory,通过调用openSessionFromDataSource()生成SqlSession,下面我们看一下具体的实现逻辑:
在这里插入图片描述
这里有一点需要注意,由于全局二级缓存是默认开启的(namspace缓存是默认不开启的,这里需要注意,具体默认值可见上篇文章),因此此处返回的执行器为CachingExecutor:在这里插入图片描述
通过上文分析,我们最终获取到了DefaultSqlSession对象,后续将通过该对象执行我们的具体sql.
这里需要注意一下SqlSession和Execute的区别:

  • SqlSession: 对外提供了用户和数据库之间交互需要的所有方法,隐藏了底层的细节。默认实现类是DefaultSqlSession.
  • Executor: SqlSession向用户提供操作数据库的方法,但和数据库操作有关的职责都会委托给Executor.
    如下图所示,Executor有若干个实现类,为Executor赋予了不同的能力,大家可以根据类名,自行学习每个类的基本作用
    在这里插入图片描述

Mapper的获取

通过上文获取到的SqlSession对象调用getMapper方法获取Mapper接口的实现对象,
在这里插入图片描述
Config.getMapper内部是通过mapperRegistry.getMapper(type, sqlSession)方法来获取具体的实现类并且生成了动态代理对象,如下图:
在这里插入图片描述

接口调用的核心逻辑

通过上文可知,我们最终获取到了mapper对应的动态代理对象MapperProxy(动态代理对象是在运行时生成的,我们现在用该对象代替,描述不是太准确,大家知道这个意思就可以了),通过调用方法接口方法,我们会进入invoke(…)中,创建MapperMethod对象并调用execute(…)方法
MapperMethod创建完毕后,根据Statment的不同类型,会进入SqlSession的不同方法中,如果是Select语句的话,最后会执行到SqlSession的selectList,代码如下所示:
在这里插入图片描述
SqlSession把具体的查询职责委托给了Executor。如果二级缓存没有关闭的话,首先会进入CachingExecutor的query方法。代码如下所示
在这里插入图片描述
在上述代码中,根据传入的参数生成CacheKey,通过分析我们发现只要两个SQL的
Statement Id + Offset + Limmit + Sql + Params+Environment id相等就认为是相同sql.
接着CachingExecutor的query方法继续走,首先会从MappedStatement中获得在配置初始化时赋予的Cache,Cache本质上是使用装饰器模式生成,具体的装饰链是:

SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache

以下是具体这些Cache实现类的介绍,他们的组合为Cache赋予了不同的能力:

  • SynchronizedCache:同步Cache,实现比较简单,直接使用synchronized修饰方法。
  • LoggingCache:日志功能,装饰类,用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。
  • SerializedCache:序列化功能,将值序列化后存到缓存中。该功能用于缓存返回一份实例的Copy,用于保存线程安全。
  • LruCache:采用了Lru算法的Cache实现,移除最近最少使用的Key/Value。
  • PerpetualCache: 作为为最基础的缓存类,底层实现比较简单,直接使用了HashMap。

然后是判断是否需要刷新缓存在这里插入图片描述
在默认的设置中SELECT语句不会刷新缓存,insert/update/delte会刷新缓存(上篇文章有讲解)。进入该方法。代码如下所示在这里插入图片描述
MyBatis的CachingExecutor持有了TransactionalCacheManager,即上述代码中的tcm。
TransactionalCacheManager中持有了一个Map,代码如下所示:在这里插入图片描述
CachingExecutor会默认使用他包装初始生成的Cache,作用是如果事务提交,对缓存的操作才会生效,如果事务回滚或者不提交事务,则不对缓存产生影响
TransactionalCache中持有以下对象用来达到目的如下所示:在这里插入图片描述
在TransactionalCache中,通过以下代码来清空缓存,既将未提交的缓存清理掉
在这里插入图片描述
分析TransactionalCache代码发现,只有调用了commit方法后才会将数据刷新到缓存中,因此,即便你开启了缓存但没有手动调用commit方法,二级缓存也不会生效,代码如下:在这里插入图片描述
后续的查询操作会重复执行这套流程。如果是insert|update|delete的话,会统一进入CachingExecutor的update方法,其中调用了这个函数flushCacheIfRequired(ms),这也解释了为什么除了查询都会更新缓存这个操作的产生。

如果二级缓存没有查到对应的数据,则继续调用BaseExecutor的query方法,如下所示,在查询方法中会将结果集存储到一级缓存中在这里插入图片描述
接着调用SimpleExecutor的doQuery方法,StatementHandler是Mybatis四大对象之一,该对象主要是用于执行SQL语句,对sql语句进行增强,在新创建StatementHandler时,内部设置了ParameterHandler和ResultSetHandler,这两个对象是四大对象的另外两个,也可以通过自定义插件对其进行增强,到此处,Mybatis的四大对象已分析完毕,他们主要是可以通过自定义插件对其进行扩展,大家注意一下就好了在这里插入图片描述
RoutingStatementHandler内部会通过StatementType值返回不同的Handler,它的作用就是起一个路由作用,我们主要分析预编译的Handler,既PreparedStatement在这里插入图片描述
接着调用query方法,进入DefaultResultSetHandler.handleResultSets方法中,
在这里插入图片描述
handleResultSet就是将我们的结果集转换为我们配置的返回类型,这一块大家后续自己了解一下就好了,这里不再进行分析了

插件的实现

首先,我们看一个我们自定义的插件类:
在这里插入图片描述
由上文可知,我们的四大对象都是通过插件的方式来进行扩展的,那么我们接下来就分析插件的实现原理是什么,本文我们从StatementHandler的插件实现进行推进在这里插入图片描述
interceptorChain的值是通过标签进行配置的,而对应的赋值操作是在配置文件解析时时候放进去的,详情可查看上篇文章
插件的实现主要通过Plugin.wrap(target, this)来进行实现,下面我们分析一下实现源码在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此处可能会有疑问,是怎么实现推进的呢?这就要说methon这个变量的妙用了,因为所有的代理对象都实现了StatementHandler接口,因此在推进时,当定义的插件有多个时,只需要传入不同的实例对象就可以了,在整个推进过程中,除了最后一次调用的是RoutingStatementHandler的方法,其它的都是调用的代理对象的方法,在这里插入图片描述

全文总结

mybatis的执行流程我们已分析完毕,总结来说呢,整个 Mybatis 的查询流程就是 封装了 JDBC 原生代码,并做了 JDBC 没有为我们做的 结果集的类型转换。最后我们附一张执行流程图来结束本章内容:
在这里插入图片描述
如有不准确的地方,欢迎指正,谢谢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值