MyBatis 一级缓存机制

250 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

一级缓存

1、一级缓存的组成

MyBatis 开启一次和数据库的会话,MyBatis 会创建出一个SqlSession对象表示一次数据库会话

SqlSession:只是一个MyBatis对外的接口,SqlSession将它的工作交给了Executor执行器这个角色来完成,负责完成对数据库的各种操作

Executor:执行器接口类,负责完成对数据库的各种操作,以及维护缓存信息

Cache:缓存接口类,处理缓存信息

PerpetualCacheCache接口实现类,通过一个简单的HashMap实现缓存

三者之间关系

SqlSession的实现类定义一个成员变量ExecutorExecutor的实现类BaseExecutor拥有Cache接口的一个实现类PerpetualCache

image.png

2、什么是一级缓存

前提

在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

目的

减少资源的浪费

实现

1、MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来

2、当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户

3、否则,从数据库读取数据,将查询结果存入缓存并返回给用户。

3、一级缓存的生命周期

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

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

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

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

1、spirng项目开启事务时,同一个事务使用同一个SqlSession,

2、未开启事务时,每次请求都会关闭之前的SqlSession,再创建新的SqlSession,所以导致一级缓存完全没有使用到。

4、一级缓存工作流程

  1. 对于某个查询,根据statementIdparamsrowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;

  2. 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;

  3. 如果命中,则直接将缓存结果返回;

  4. 如果没命中:

    4.1 去数据库中查询数据,得到查询结果;

    4.2 将key和查询到的结果分别作为key,value对存储到Cache中;

    4.3 将查询结果返回;

  5. 结束。

5、CacheKey的定义

组成:statementId + rowBounds + 传递给JDBC的SQL + 传递给JDBC的参数值

statementId:代表着将执行什么样的Sql

rowBounds:查询时要求的结果集中的结果范围;MyBatis自身提供的分页功能是通过RowBounds来实现的,它通过rowBounds.offset和rowBounds.limit来过滤查询出来的结果集

传递给JDBC的SQL:调用JDBC的时候,传入的SQL语句要完全相同

传递给JDBC的参数值:传递给JDBC的参数值也要完全相同

目的

1、根据CacheKey作为key,去Cache缓存中查找缓存结果;

2、如果查找缓存命中失败,则通过此CacheKey作为key,将从数据库查询到的结果作为value,组成key,value对存储到Cache缓存中。

6、性能问题

1、MyBatis对会话(Session)级别的一级缓存设计的比较简单,就简单地使用了HashMap来维护,并没有对HashMap的容量和大小进行限制。

如果一直使用某一个SqlSession对象查询数据,这样会不会导致HashMap太大,而导致OOM错误?

MyBatis这样设计也有它自己的理由:

a. 一般而言SqlSession的生存时间很短。一般情况下使用一个SqlSession对象执行的操作不会太多,执行完就会消亡;

b. 对于某一个SqlSession对象而言,只要执行update操作(update、insert、delete),都会将这个SqlSession对象中对应的一级缓存清空掉,所以一般情况下不会出现缓存过大,影响JVM内存空间的问题;

c. 可以手动地释放掉SqlSession对象中的缓存。

2、一级缓存是一个粗粒度的缓存,没有更新缓存和缓存过期的概念

MyBatis的一级缓存就是使用了简单的HashMap,MyBatis只负责将查询数据库的结果存储到缓存中去, 不会去判断缓存存放的时间是否过长、是否过期,因此也就没有对缓存的结果进行更新这一说了。

根据特性,需要注意: 1、对于数据变化频率很大,并且需要高时效准确性的数据要求,我们使用SqlSession查询的时候,要控制好SqlSession的生存时间,SqlSession的生存时间越长,它其中缓存的数据有可能就越旧,从而造成和真实数据库的误差;同时对于这种情况,用户也可以手动地适时清空SqlSession中的缓存;

2、对于只执行、并且频繁执行大范围的select操作的SqlSession对象,SqlSession对象的生存时间不应过长。