【深入MyBatis】一级缓存机制

前言

MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上。MyBatis提供了一级缓存、二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高系统的性能。

一.Mybatis的几个核心概念

1.SqlSession : 代表和数据库的一次会话,向用户提供了操作数据库的方法。
2.MappedStatement: 代表要发往数据库执行的指令,可以理解为是Sql的抽象表示。
3.Executor: 具体用来和数据库交互的执行器,接受MappedStatement作为参数。
4.映射接口: 在接口中会将要执行的Sql用一个方法来表示,具体的Sql写在映射文件中。
5.映射文件: 可以理解为是Mybatis编写Sql的地方,通常来说每一张单表都会对应着一个映射文件,在该文件中会定义Sql语句入参和出参的形式。

二.getMapper()的使用时的内部执行流程

mapper元素代表这个文件是一个映射文件,使用namespace和具体的映射接口绑定起来,namespace的值就是这个接口的全限定类名。select|insert|update|delete代表的是Sql语句,映射接口中定义的每一个方法也会和映射文件中的语句通过id的方式绑定起来,方法名就是语句的id,同时会定义语句的入参和出参,用于完成和Java对象之间的转换。

在Mybatis初始化的时候,每一个语句都会使用对应的MappedStatement代表,使用namespace+语句本身的id来代表这个语句。

SELEC  Tid,name,age  FROM  student  WHERE  id= #{id}

在Mybatis执行时,会进入对应接口的方法,通过类名加上方法名的组合生成id,找到需要的MappedStatement,交给执行器使用。

三.什么是一级缓存? 为什么使用一级缓存?

每当我们使用MyBatis开启一次和数据库的会话时,MyBatis会创建出一个SqlSession对象表示一次数据库会话;在对数据库的会话过程中,我们可能会反复执行完全相同的查询语句;
如果不采取一些措施,我们每一次查询都会查询一次数据库,而如果在极短的时间内做了很多次相同的查询操作,那么这些查询返回的结果很可能相同,但由于对数据库进行一次操作的开销是很大的,这样会极大的浪费资源;

为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。

一个SqlSession对象中创建一个本地缓存(local cache),对于每一次查询,都会首先尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户否则从数据库读取数据,将查询结果存入缓存并返回给用户。

一级缓存配置
<setting name="localCacheScope" value="SESSION"/>

四.MyBatis的一级缓存是如何组织的?


SqlSession只是一个MyBatis对外的接口,SqlSession将它的工作交给了Executor执行器这个角色来完成,负责完成对数据库的各种操作。当创建了一个SqlSession对象时,MyBatis会为这个SqlSession对象创建一个新的Executor执行器,而缓存信息就被维护在这个Executor执行器中,MyBatis将缓存和对缓存相关的操作封装成了Cache接口中。


14534869-ea5af32bd76b133f.png
SqlSession和Executor以及Cache关系图

如上述的类图所示,Executor接口的实现类BaseExecutor中拥有一个Cache接口的实现类PerpetualCache,则对于BaseExecutor对象而言,它将使用PerpetualCache对象维护缓存。

PerpetualCache实现原理其实很简单,其内部就是通过一个简单的HashMap<k,v> 来实现的,没有其他的任何限制

五.一级缓存的生命周期

[1]. MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

[2]. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;

[3]. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;

[4].SqlSession中执行了任何一个DML对数据库进行操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;

 SqlSession 一级缓存的工作流程:

6263336-b9f947392aaadaaa.png
一级缓存执行的时序图

1.对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
2. 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
3. 如果命中,则直接将缓存结果返回;
4. 如果没命中:       
    4.1  去数据库中查询数据,得到查询结果;       
    4.2  将key和查询到的结果分别作为key,value对存储到Cache中;       
    4.3. 将查询结果返回;
5. 判断缓存级别是否为STATEMENT级别,如果是的话,清空本地缓存


5. Cache接口的设计以及CacheKey的定义

MyBatis内部有很多Cache接口的实现,一级缓存只会涉及到一个PerpetualCache子类.

Cache最核心的实现其实就是一个Map,将本次查询使用的特征值作为key,将查询结果作为value存储到Map中.

MyBatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询:

1. 传入的 statementId
2. 查询时要求的结果集中的结果范围 (结果的范围通过rowBounds.offset和rowBounds.limit表示)
3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )
4. 传递给java.sql.Statement要设置的参数值

Tips:
    MyBatis自身提供的分页功能是通过RowBounds来实现的,它通过rowBounds.offsetrowBounds.limit来过滤查询出来的结果集,这种分页功能是基于查询结果的再过滤,而不是进行数据库的物理分页;

综上所述,CacheKey由以下条件决定:
                statementId  + rowBounds  + 传递给JDBC的SQL  + 传递给JDBC的参数值

构建CacheKey的过程实际上就是构造其hashCode的过程

6.总结

1.Mybatis一级缓存的生命周期和SqlSession一致。

2.Mybatis的缓存是一个粗粒度的缓存,没有更新缓存和缓存过期的概念,同时只是使用了默认的、简单的hashmap,也没有做容量上的限定。

3.Mybatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,有操作数据库写的话,会引起脏数据,建议是把一级缓存的默认级别设定为Statement,即不使用一级缓存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值