Mybatis一二级缓存的理解
频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相同的查询语句,完全可以把查询结果存储起来,下次查询同样的内容的时候直接从内存中获取数据即可,这样在某些场景下可以大大提升查询效率。
MyBatis的缓存分为两种:
- 一级缓存,一级缓存是SqlSession级别的缓存,对于相同的查询,会从缓存中返回结果而不是查询数据库
- 二级缓存,二级缓存是Mapper级别的缓存,定义在Mapper文件的<cache>标签中并需要开启此缓存,多个Mapper文件可以共用一个缓存,依赖<cache-ref>标签配置
SqlSession>DefaultSqlSession(selectList)>this.executor.query>Executor(一级缓存BaseExecutor, 二级缓存CachingExecutor)
CacheKey判断两次查询条件是否一致。
一级缓存:/executor/BaseExecutor.class
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(this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if(this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
list = resultHandler == null?(List)this.localCache.getObject(key):null;
if(list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if(this.queryStack == 0) {
Iterator i$ = this.deferredLoads.iterator();
while(i$.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if(this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
一级缓存三个结论:
- MyBatis的一级缓存是SqlSession级别的,但是它并不定义在SqlSessio接口的实现类DefaultSqlSession中,而是定义在DefaultSqlSession的成员变量Executor中,Executor是在openSession的时候被实例化出来的,它的默认实现为SimpleExecutor
- MyBatis中的一级缓存,与有没有配置无关,只要SqlSession存在,MyBastis一级缓存就存在,localCache的类型是PerpetualCache,它其实很简单,一个id属性+一个HashMap属性而已,id是一个名为"localCache"的字符串,HashMap用于存储数据,Key为CacheKey,Value为查询结果
- MyBatis的一级缓存查询的时候默认都是会先尝试从一级缓存中获取数据的,但是我们看第6行的代码做了一个判断,ms.isFlushCacheRequired(),即想每次查询都走DB也行,将<select>标签中的flushCache属性设置为true即可,这意味着每次查询的时候都会清理一遍PerpetualCache,PerpetualCache中没数据,自然只能走DB。增删改没有一级缓存。
//缓存判断查询条件是否一致:
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if(this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());//判断id属性是否相同
cacheKey.update(Integer.valueOf(rowBounds.getOffset()));//判断Offset属性是否相同
cacheKey.update(Integer.valueOf(rowBounds.getLimit()));//判断Limit属性是否相同
cacheKey.update(boundSql.getSql());//判断sql属性是否相同
List parameterMappings = boundSql.getParameterMappings();//后面都是判断参数是否相同
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
if(parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if(boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if(parameterObject == null) {
value = null;
} else if(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = this.configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
return cacheKey;
}
}
一级缓存从四组共五个条件判断两次查询是相同的:
- <select>标签所在的Mapper的Namespace+<select>标签的id属性
- RowBounds的offset和limit属性,RowBounds是MyBatis用于处理分页的一个类,offset默认为0,limit默认为Integer.MAX_VALUE
- <select>标签中定义的sql语句
- 输入参数的具体参数值,一个int值就update一个int,一个String值就update一个String,一个List就轮询里面的每个元素进行update
即只要两次查询满足以上三个条件且没有定义flushCache="true",那么第二次查询会直接从MyBatis一级缓存PerpetualCache中返回数据,而不会走DB。
MyBatis二级缓存的生命周期即整个应用的生命周期,应用不结束,定义的二级缓存都会存在在内存中。
从这个角度考虑,为了避免MyBatis二级缓存中数据量过大导致内存溢出,MyBatis在配置文件中给我们增加了很多配置例如size(缓存大小)、flushInterval(缓存清理时间间隔)、eviction(数据淘汰算法)来保证缓存中存储的数据不至于太过庞大。
二级缓存:/executor/CachingExecutor.class
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();//Cache是从MappedStatement中获取到的,而MappedStatement又和每一个<insert>、<delete>、<update>、<select>绑定并在MyBatis启动的时候存入Configuration中:
if(cache != null) {
this.flushCacheIfRequired(ms);//根据flushCache=true或者flushCache=false判断是否要清理二级缓存
if(ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, parameterObject, boundSql);//保证MyBatis二级缓存不会存储存储过程的结果
List list = (List)this.tcm.getObject(cache, key);//tcm装饰器模式,创建一个事物缓存TranactionalCache,持有Cache接口,Cache接口的实现类就是根据我们在Mapper文件中配置的<cache>创建的Cache实例
if(list == null) {
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}//如果没有从MyBatis二级缓存中拿到数据,那么就会查一次数据库,然后放到MyBatis二级缓存中去
return list;
}
}
//优先读取以上二级缓存,query方法优先读取默认实现的一级缓存。
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
MyBatis支持三种类型的二级缓存:
- MyBatis默认的缓存,type为空,Cache为PerpetualCache
- 自定义缓存
- 第三方缓存
开启二级缓存:
<settings>//mybatis.cfg.xml
<!-- 开启二级缓存 默认值为true -->
<setting name="cacheEnabled" value="true"/>
</settings> <mapper namespace="cn.sxt.vo.user.mapper">//mapper.xml
<!-- 开启本mapper namespace下的二级缓存 -->
<cache></cache>
select a.col1, a.col2, a.col3, b.col1, b.col2, b.col3 from tableA a, tableB b where a.id = b.id;
对于tableA与tableB的操作定义在两个Mapper中,分别叫做MapperA与MapperB,即它们属于两个命名空间,如果此时启用缓存:
- MapperA中执行上述sql语句查询这6个字段
- tableB更新了col1与col2两个字段
- MapperA再次执行上述sql语句查询这6个字段(前提是没有执行过任何insert、delete、update操作)
此时问题就来了,即使第(2)步tableB更新了col1与col2两个字段,第(3)步MapperA走二级缓存查询到的这6个字段依然是原来的这6个字段的值,因为我们从CacheKey的3组条件来看:
- <select>标签所在的Mapper的Namespace+<select>标签的id属性
- RowBounds的offset和limit属性,RowBounds是MyBatis用于处理分页的一个类,offset默认为0,limit默认为Integer.MAX_VALUE
- <select>标签中定义的sql语句
对于MapperA来说,其中的任何一个条件都没有变化,自然会将原结果返回。
这个问题对于MyBatis的二级缓存来说是一个无解的问题,因此使用MyBatis二级缓存有一个前提:
必须保证所有的增删改查都在同一个命名空间下才行。
Mybatis一二级缓存的理解的更多相关文章
- 【MyBatis源码解析】MyBatis一二级缓存
MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...
- 带你了解MyBatis一二级缓存
在对数据库进行噼里啪啦的查询时,可能存在多次使用相同的SQL语句去查询数据库,并且结果可能还一样,这时,如果不采取一些措施,每次都从数据库查询,会造成一定资源的浪费,所以Mybatis中提供了一级缓存 ...
- mybatis 源码分析(四)一二级缓存分析
本篇博客主要讲了 mybatis 一二级缓存的构成,以及一些容易出错地方的示例分析: 一.mybatis 缓存体系 mybatis 的一二级缓存体系大致如下: 首先当一二级缓存同时开启的时候,首先命中 ...
- Mybatis一级缓存和二级缓存 Redis缓存
一级缓存 Mybatis的一级缓存存放在SqlSession的生命周期,在同一个SqlSession中查询时,Mybatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对 ...
- Mybatis学习(6)动态加载、一二级缓存
一.动态加载: resultMap可以实现高级映射(使用association.collection实现一对一及一对多映射),association.collection具备延迟加载功能. 需求: 如 ...
- 认识Mybatis的一二级缓存
认识Mybatis的一二级缓存 一次完整的数据库请求,首先根据配置文件生成SqlSessionFactory,再通过SqlSessionFactory开启一次SqlSession,在每一个SqlSes ...
- Mybatis的一级缓存和二级缓存的理解以及用法
程序中为什么使用缓存? 先了解一下缓存的概念:原始意义是指访问速度比一般随机存取存储器快的一种RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术.对于我们编程来说,所谓的 ...
- SpringBoot 下 mybatis 的缓存
背景: 说起 mybatis,作为 Java 程序员应该是无人不知,它是常用的数据库访问框架.与 Spring 和 Struts 组成了 Java Web 开发的三剑客--- SSM.当然随着 Spr ...
- MyBatis 一级缓存和二级缓存及ehcache整合
一级缓存 什么是缓存?? 缓存是存储在内存(cache)中的数据,一般情况都存在内存,在内存数据存储满了,会存储到硬盘上(disk),或是在我们进行一些操作和配置也可以把缓存存储到磁盘中. 缓存的作用 ...
随机推荐
- app后端开发系列文章文件夹
一点废话 每个程序猿心中,都有一个大牛梦.我们在晨曦之光中敲击着代码,在寒冬覆雪中思考着0与1. 夏练三伏 冬练三九这说的就是我们这群[江湖]中人.在这里我们门派林立,C语言派历史悠久,在程序界就是嵩 ...
- JavaScript 推断浏览器类型及32位64位
JS推断出版本号以及浏览器类型 <script type="text/javascript"> var Sys = {}; var ua = navigator.use ...
- hadoop学习笔记——环境搭建
基础环境准备: 系统:(VirtualBox) ubuntu-12.04.2-desktop-i386.iso hadoop版本:hadoop-0.20.203.0rc1.tar.gz jdk版本:j ...
- git 怎样删除远程仓库的最近一次错误提交?
假设你有3个commit如下: commit 3 commit 2 commit 1 其中最后一次提交commit 3是错误的,那么可以执行: git reset --hard HEAD~1 你会发现 ...
- Windows 7 & Ubuntu 14.04完美双系统安装及系统引导配置----校园网Mentohust配置
本文写于完美安装双系统之后,所以图片会不全然.主要目的是总结下注意事项.备用. 一.Win7-64-旗舰版U盘安装 win7-64-旗舰版纯净版下载,下载安装后仅仅有1个驱动人生! 附刻盘工具激活工具 ...
- ECharts整合HT for Web的网络拓扑图应用
ECharts图形组件在1.0公布的时候我就已经有所关注.今天在做项目的时候遇到了图标的需求,在HTfor Web上也有图形组件的功能.可是在尝试了下详细实现后,发现HT for Web的图形组件是以 ...
- Andorid——ubuntu下的 NDK / JNI
之前一直有接触源代码里面的JNI体系,知道个大概,仅仅管调进了哪个C/C++的接口,如今记录学习下. 撰写不易,转载请注明出处:http://blog.csdn.net/jscese/article/ ...
- SAP-ABAP系列 第一篇SAP简介
第一篇 SAP简介 SAP全名为System Application and Products in Data Processing.SAP目前是全世界排名第一的RP软件,号称“全球最大的企业管理解决 ...
- 功能强大的Xcode辅助工具Faux Pas:帮你找到各种隐形的bug
本文转载至 http://www.cocoachina.com/industry/20140804/9307.html Faux Pas(Beta版下载地址)是一个Xcode辅助工具,用以检查Xcod ...
- java会不会出现内存泄露
1 什么是java内存泄露 当java中的对象生命周期已经结束,本应该释放,但是却长时间不能被释放时,也就是说,内存被浪费了,就是内存泄露. 2 java内存泄露的根本原因 长生命周期的对象中持有短生 ...