Mybatis缓存
缓存就是内存中的数据,常源自于对数据库查询结果的保存,使用缓存,我们可以减少跟数据库交互的次数,进而提高响应速度。
Mybatis提供了对缓存的支持,分为一级缓存和二级缓存,可以通过下图和下表来理解:
Mybatis一级缓存
基本概念
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间的缓存数据区域(HashMap)是互不影响的。
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。
Mybatis中,一级缓存默认是开启的。
一级缓存又称为查询缓存。
举例
场景1:在一个SqlSession中,对数据表根据id进行连续两次查询,第一次查询时会执行sql语句,第二次查询时则会直接从缓存中读取数据,无需再次执行sql语句。
场景2:在一个SqlSession中,对数据表根据id进行两次查询,中间进行了一次update操作。这样,两次查询都会执行查询的sql语句。因为两次查询期间SqlSession执行了commit操作(执行新增/更新/删除),会清空SqlSession中的一级缓存,这样做的目的是为了让缓存中存储的是最新的信息,避免脏读。
如下图所示:
总结
MyBatis一级缓存的生命周期和SqlSession一致。
MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
Mybatis二级缓存
基本概念
在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。二级缓存是mapper(namespace)级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
总结
MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
通过阅读源码来了解MyBatis一级缓存
SqlSession这个类共有以下这些方法:
根据方法名可知,只有clearCache与缓存有关系。SqlSession的实现类是DefaultSqlSession,实现类中的clearCache方法如下:
这里调用了执行器Executor的clearLocalCache接口来清除本地缓存(即一级缓存),执行器的实现类为BaseExecutor,其clearLocalCache方法为:
这里的localCache即为本地缓存,定义如下:
PerpetualCache的clear方法如下:
结合上述分析可得出如下流程图:
根据上述代码分析可知,PerpetualCache的clear方法会清空缓存,这里的cache是这么定义的:
也就是说,cache是一个map,cache.clear()就是map.clear()。即:缓存就是存放在本地的一个map对象,每一个SqlSession都会存放一个map对象的引用。