Mybatis缓存相关知识

MyBatis中的缓存相关类都在cache包下面,而且定义了一个顶级接口Cache,默认只有一个实现类PerpetualCache,PerpetualCache中是内部维护了一个HashMap来实现缓存。

在MyBatis中存在两种缓存,即一级缓存二级缓存

一级缓存:一级缓存也称本地缓存,session级别的缓存

二级缓存:二级缓存是全局缓存,是一个基于namespace级别的缓存,作用域更为广泛,不局限于一个sqlSession,可以在多个sqlSession之间共享,mybatis的二级缓存默认也是开启的,但是由于它的作用域是namespace,所以还需要在mapper.xml中开启才能生效,只需要加入cache标签即可。

作用顺序为二级缓存>一级缓存>数据库

一级缓存作用域仅限于同一sqlSession中,无法感知到其他sqlSession中的数据变更,所以极易产生脏数据,而二级缓存可以通过cache-ref让多个mapper.xml共享一个namespace,从而实现缓存共享,但多表联动查询的时候会比较麻烦,由此,在生产环境的时候可以关闭一级缓存(statement级别),有需要的话可以打开二级缓存。如果是分布式应用的话, 这里我们不建议开启

一级缓存

一级缓存也叫本地缓存,在MyBatis中,一级缓存是在会话(SqlSession)层面实现的,这就说明一级缓存作用范围只能在同一个SqlSession中,跨SqlSession是无效的。在同一个 SqlSession中,两次执行相同的 sql 查询,第二次不再从数据库查询。

MyBatis中一级缓存是默认开启的,不需要任何配置。有两个级别的选项:SESSION或者STATEMENT,默认是SESSION级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。一种是STATEMENT级别,可以理解为缓存只对当前执行的这一个Statement有效。
配置:

<setting name="localCacheScope" value="SESSION"/>

原理:一级缓存采用 Hashmap 存储,mybatis 执行查询时,从缓存中查询,如果缓存中没有从数据库查询。如果该 SqlSession 执行 clearCache()提交 或者增加 删除 修改操作。

工作流程

query操作时,这个缓存的Hashmap的key生成方法是:

CacheKey cacheKey = new CacheKey();
//MappedStatement的Id
cacheKey.update(ms.getId());
//分页
cacheKey.update(rowBounds.getOffset());
//SQL的limit
cacheKey.update(rowBounds.getLimit());
//SQL本身
cacheKey.update(boundSql.getSql());
//后面是update了sql中带的参数
cacheKey.update(value);

即:将MappedStatement的Id、SQL的offset、SQL的limit、SQL本身以及SQL中的参数传入了CacheKey这个类,最终构成CacheKey。只要两条SQL的下列五个值相同,即可以认为是相同的SQL。

Statement Id + Offset + Limmit + Sql + Params

如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。

insert/delete/update操作时:

SqlSession的insert方法和delete方法,都会统一走update的流程,代码如下所示:

@Override
public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }
   @Override
  public int delete(String statement) {
    return update(statement, null);
}

update方法也是委托给了Executor执行。BaseExecutor的执行方法如下所示:

@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //清空本地缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
}

每次执行update前都会清空localCache。

一级缓存失效的四种情况:
  • 不同的SqlSession对应不同的一级缓存
  • 同一个SqlSession但查询条件不同
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作
  • 同一个SqlSession两次查询期间手动清空了缓存
总结
  • MyBatis一级缓存的生命周期和SqlSession一致。
  • MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
  • MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
二级缓存

在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询。

二级缓存工作流程

二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。

当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

二级缓存配置

要正确的使用二级缓存,需完成如下配置的。

在MyBatis的配置文件中开启二级缓存。

<setting name="cacheEnabled" value="true"/>

在MyBatis的映射XML中配置cache或者 cache-ref 。
cache标签用于声明这个namespace使用二级缓存,并且可以自定义配置。

<cache/>
  • type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
  • eviction: 定义回收的策略,常见的有FIFO,LRU。
  • flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
  • size: 最多缓存对象的个数。
  • readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
  • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
  • cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。
<cache-ref namespace="mapper.StudentMapper"/>
总结
  • MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  • MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  • 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
参考
  1. 聊聊MyBatis缓存机制
  2. 5 分钟,彻底掌握 MyBatis 缓存!
  3. mysql的一级缓存机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值