mybatis源码学习(三)-一级缓存二级缓存
本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点
1.一级缓存配置信息
2.一级缓存源码学习笔记
3.二级缓存配置信息
4.二级缓存源码
5.一级缓存、二级缓存总结
1.一级缓存配置:
一级缓存是SqlSession级别的,同一个sqlSession执行多次相同的查询语句时,第二次会从缓存中获取数据,不查询数据库;一级缓存是无法关闭的,默认是SESSION级别
mybatis一级缓存默认是开启的,一级缓存有两个配置值,SESSION级别和STATEMENT级别,这两个级别的区别是:
如果是SESSION级别(默认值),那么在同一个sqlSession中,都会共享这个缓存
如果是STATEMENT级别,那么会把一级缓存中的缓存清空,也就是说,同一个sqlSession,多次请求同一个sql,每次都会查询数据库
默认是session级别,此时为开启二级缓存:

如果把一级缓存的级别设置为STATEMENT,那么同一个sqlSession执行多次相同的SQL,都会查询数据库:

那下面,我们来源码中验证
2.一级缓存源码:
1.首先会根据statement等参数,生成缓存中需要用到的key org.apache.ibatis.executor.CachingExecutor#createCacheKey
2.根据key从一级缓存中获取值,一级缓存是PerpetualCache这个类
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)
3.如果从一级缓存中取到值,就不查询数据库;否则查询数据库
org.apache.ibatis.executor.BaseExecutor#handleLocallyCachedOutputParameters
4.如果一级缓存中没有取到值,就从数据库中查询,并将数据库中查询到的数据,存到一级缓存中
org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
5.判断缓存级别是否是STATEMENT,如果是,将一级缓存清空
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
这段源码中,第14行,是尝试从一级缓存中根据key,取值;如果不为null,就从一级缓存中取值,第18行,是从数据中查询数据;
在 queryFromDatabase()方法中,会将数据库中查询到的数据,put到一级缓存中
源码中的第29行,就是对一级缓存是否是STATEMENT级别做判断,如果是,就clear;
我们再来说,insert、update、delete,这三种操作,都会走update,
org.apache.ibatis.executor.BaseExecutor#update
@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);
}
可以看到,执行doUpdate()方法之前,会先clear一级缓存
3.二级缓存配置
二级缓存是mapper级别的,需要配置才会开启
需要在mybatis的配置文件中,增加以下配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
并且在mapper.xml文件中增加
<cache></cache>
这样,二级缓存就开启了
4.二级缓存源码
二级缓存的源码,首先我们要看 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) 这个方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
在第7行,首先会判断,当前sql,是否开启useCache这个属性,对于select 默认是true;update、insert、delete默认是 false;这个点的源码是在解析xml的时候,最终会把每个select、update、insert、delete节点
build成一个mappedStatement,在build之前,有这么一段代码
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
这里的意思是:如果是select,那么useCache就是true,否则就是false
接着上面的说,第10行会尝试从二级缓存中获取数据,这里的调用链是这样的

从源码中,可以看到,二级缓存先于以及缓存,如果二级缓存中没有取到值,会充以及缓存中国取,一级缓存中没有取到,再从数据库查询数据
这个调用链这几个cache,还没有搞明白,具体的作用,后面学习之后,会更新;
二级缓存有一个点,需要明确:二级缓存只有在sqlSession执行commit的时候,才会更新;否则是不生效的

这个是commit的时候,二级缓存Put的调用链
5.总结
一级缓存是sqlSession级别的,二级缓存是mapper级别的,二级缓存的粒度更细,
一级缓存无法关闭,二级缓存可以开启或者关闭
一级缓存如果在分布式的场景下,可能会存在脏数据;
如果使用缓存,感觉还是Redis靠谱吧;
mybatis源码学习(三)-一级缓存二级缓存的更多相关文章
- mybatis源码学习:一级缓存和二级缓存分析
目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...
- mybatis源码学习:基于动态代理实现查询全过程
前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...
- mybatis源码学习:插件定义+执行流程责任链
目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...
- Mybatis源码学习第六天(核心流程分析)之Executor分析
今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...
- mybatis源码学习(一) 原生mybatis源码学习
最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...
- Mybatis源码解析(三) —— Mapper代理类的生成
Mybatis源码解析(三) -- Mapper代理类的生成 在本系列第一篇文章已经讲述过在Mybatis-Spring项目中,是通过 MapperFactoryBean 的 getObject( ...
- 手把手带你阅读Mybatis源码(三)缓存篇
前言 大家好,这一篇文章是MyBatis系列的最后一篇文章,前面两篇文章:手把手带你阅读Mybatis源码(一)构造篇 和 手把手带你阅读Mybatis源码(二)执行篇,主要说明了MyBatis是如何 ...
- Mybatis源码学习之整体架构(一)
简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...
- Mybatis源码学习第八天(总结)
源码学习到这里就要结束了; 来总结一下吧 Mybatis的总体架构 这次源码学习我们,学习了重点的模块,在这里我想说一句,源码的学习不是要所有的都学,一行一行的去学,这是错误的,我们只需要学习核心,专 ...
随机推荐
- 关于typedef和struct
在struct中使用自身,需要加struct关键字,无论带不带typedef,例如: struct A { int a; struct A *pA; }; 在定义struct方面尽量不要使用typed ...
- 解决node.js使用fs读取文件出错
今天配接口,使用fs模块读取json出现了错误'no such file or directory',然后经查终于解决,特此记录. 使用nodejs的fs模块读取文件时习惯用相对路径,但是运行的时 ...
- post方式实现导出/下载文件
项目需求: 前端需要传入过多的参数给后端,get地址栏不行,只能接受post方式去导出数据 1.get的下载方式 通常下载方式如下: let url = xxxx.action?a=xx&b= ...
- 使用Magicodes.SwaggerUI快速配置SwaggerUI以及设置API分组
Magicodes.SwaggerUI 快速配置和集成SwaggerUI 特点 通过配置文件简单配置即可完成SwaggerUI的API格式JSON生成和集成 支持API分组和隐藏 支持自定义页面和验证 ...
- Unity3-各个内置面板,对象说明
*在Inspector面板中,是表示每个物体(诸如摄像机,圆柱,正方体)的组件. 组件包含: 1.Transform,在第一节当中,可以用于变换物体的位置.每个物体对象都有. 2.cube,网格,对于 ...
- 关于virtualbox虚拟电脑控制台严重错误解决方法。。。
今天的我已经泪崩,忙了一天,临近下班的时候,突然发现虚拟机关了,再打开时,提示错误!!! 然后在网上查方法解决完的时候,再打开一看,完了,虚拟机里面的内容全没了...现将解决方法记录如下: 1.打开, ...
- CSPS模拟 71
全程傻眼 T1 毛衣衬 meet_in_middle.. 不再使用二分查找,而是直接枚举对面状态,虽然底数爆炸但是指数减半,复杂度是对的. T2 猫儿嗔 逆序关系有支配关系? $DAG$树.. 把逆序 ...
- 使用 element-ui 级联插件遇到的坑
需求描述[省市区三级联动] 组件:Cascader 级联选择器 后端需要所选中的地区的名字,如:['北京市', '北京市', '东城区'] 获取后端省市区具体列表的接口返回数据: // 省 - 参数1 ...
- 详解Kafka Producer
上一篇文章我们主要介绍了什么是 Kafka,Kafka 的基本概念是什么,Kafka 单机和集群版的搭建,以及对基本的配置文件进行了大致的介绍,还对 Kafka 的几个主要角色进行了描述,我们知道,不 ...
- day5-基本数据类型总结
一.数字int(..)二.字符串replace/find/join/strip/startswith/split/upper/lower/format tempalte = "i am {n ...