type
status
date
slug
summary
tags
category
icon
password
😀
这里写文章的前言: 一个简单的开头,简述这篇文章讨论的问题、目标、人物、背景是什么?并简述你给出的答案。
可以说说你的故事:阻碍、努力、结果成果,意外与转折。
 

📝 题记

缓存这个知识点在许多地方都有的,利用到好的话,对系统的很多地方查询是有很大的提升的. 可以看到,MyBatis 也是有 cache 的,那MyBatis 是怎么利用这个缓存的呢? 在 INSERT/UPDATE/DELETE/SELECT中,是不是只有SELECT的时候用到了缓存,如果是 INSERT/UPDATE/DELETE 是否会对缓存有影响?

可以看结果来分析,然后跟进源码来仔细分析.
MyBatis 是分为 一级缓存 和 二级缓存的. 那么,我们就先从一级缓存开始.
 

一级缓存

案例代码 :
这里我们是打印的查询sql的语句,再第二次再查询的时候,是
 
案例二 : 我们再第二次查询之前 加入 一个add 方法
 
案例三 : 使用二个 SqlSession 案例
可以很明显的看到 , 在第二次的时候还出现了脏数据.
这里也可以看到一级缓存是只在 SqlSession 中存在的,也就是数据库会话内部共享的.
可以看到我们说了三面的这三种情况, 具体的执行流程可以我们可以现在 案例一里面对第二次 query 进行 debug 分析操作. 当我们debug到 org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql) 的时候,可以看到 org.apache.ibatis.executor.BaseExecutor#localCache 只有一个 缓存的值的 , 根据 getObject 方法可以跟进到 org.apache.ibatis.cache.impl.PerpetualCache#cache 中来,
传入进来的 key 值是 : -1896651191:1062027004:com.iyang.mybatis.mapper.BlogMapper.selectBlog:0:2147483647:select * from tb_blog where id = ?:1:development 然后从 cache 中获取出值来, 所以这里就没有走 query 的查询语句.
这是命中缓存的情况.
下面我们来看下, 在第二次 query 之前如果执行了一个 add 方法,为什么就命中不了了呢?
这里可以大致猜测下,在执行完 add 方法后,是不是给 cache 给清除掉了,然后再去查询的时候,就查询不到了.
于是我们在 add 方法上进行 debug 查看下 :
最后我们 debug 跟进到这里 : org.apache.ibatis.executor.BaseExecutor#clearLocalCache 就可以发现
这里是有二个 clear 方法,也就是清除方法.
localCache.clear() —-> org.apache.ibatis.cache.impl.PerpetualCache#clear 对应的就是这里的清楚方法,直接调用 HashMap 的clear 方法进行清除.
所以这里可以看出在第二次调用 query 之前,如果是有 insert/update/delete 等方法的话,就会去重置这二个地方的缓存的.
MyBatis 的一级缓存的是跟随 SqlSession 的,这里是可以根据简单的案例效果看出来的.
一级缓存只是使用了一个 HashMap , 最后清除缓存的时候,也是调用 HashMap 的clear 方法
最后从案例三可以看出来,当多个 SqlSession 的时候,由于各自有存有各自的缓存,所以是很容易引起脏数据的, 将缓存级别设置为 Statement.
 

二级缓存

可以看到一级缓存的话,是局限于 SqlSession . 如果要多个 sqlSession 之间共享缓存的话,就需要开启二级缓存. 开启的话,我们在 MyBatis 配置文件中加上:
 
案例一 : 是否提交事务
 
案例二 : 中间穿插更新语句
 
这里是可以看到在更新之后并且 commit 了事务之后,后面紧跟的 sql 是去查询 数据库了的. 所以这里是可以看出来,update等操作是会去 清空对应的缓存的。
这里我们根据 案例一 的情况来分析,在开启了 二级缓存 的时候,是从哪里获取出来的数据的呢?
debug 跟进来 : org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
 
org.apache.ibatis.executor.CachingExecutor#tcm 调用这个对象的 getObject 方法获取到了我们需要的值, 跟进来又从 org.apache.ibatis.cache.decorators.TransactionalCache 的 getObject 获取出我们的值, 最后从 org.apache.ibatis.cache.decorators.TransactionalCache#delegate 获取出值, 返回回来的.
org.apache.ibatis.cache.decorators.TransactionalCache#getObject
 
MyBatis 二级缓存不适应于配置文件中存在多表查询的情况. 一般我们是单表的 cache, 由于 mybatis 的二级缓存是基于 namespace 的, 多表查询语句所在的 namespace 无法感应到其他的 namespace 中的语句对多表中设计修改,就会引发脏数据. 这个时候,可以采用 cache-ref 来做处理,但是这样的话,缓存的颗粒度就变粗了.
执行流程 : 如果开启了二级缓存的话, MyBatis 会先走二级缓存,如果二级缓存没有的话,就会去一级缓存看看,如果都没有的话,就去查询数据库.
二级缓存 : 用 org.apache.ibatis.executor.CachingExecutor 装饰了 org.apache.ibatis.executor.BaseExecutor 的子类, 委托具体职责给 delegate 之前,实现了二级缓存的查询和写入功能.
 

🤗 总结归纳

最后看 一级缓存和二级缓存,都是利用的 HashMap 这种来做到本地缓存, 只是二级缓存的作用范围比起一级缓存的话,是要大的,并且也利用了一些 装饰者 等设计模式来设计二级缓存的.
如果是部署的分布式项目的话,那么还是 得切换到 redis 这种缓存来了, 本地利用 HashMap 这种缓存满足不了的.

📎 参考文章

 
💡
有关文章的问题,欢迎您在底部评论区留言,一起交流~