Mybatis一二级缓存的理解
2018-06-17 16:45
288 查看
频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相同的查询语句,完全可以把查询结果存储起来,下次查询同样的内容的时候直接从内存中获取数据即可,这样在某些场景下可以大大提升查询效率。
二级缓存,二级缓存是Mapper级别的缓存,定义在Mapper文件的<cache>标签中并需要开启此缓存,多个Mapper文件可以共用一个缓存,依赖<cache-ref>标签配置
SqlSession>DefaultSqlSession(selectList)>this.executor.query>Executor(一级缓存BaseExecutor,二级缓存CachingExecutor)
CacheKey判断两次查询条件是否一致。
MyBatis中的一级缓存,与有没有配置无关,只要SqlSession存在,MyBastis一级缓存就存在,localCache的类型是PerpetualCache,它其实很简单,一个id属性+一个HashMap属性而已,id是一个名为"localCache"的字符串,HashMap用于存储数据,Key为CacheKey,Value为查询结果
MyBatis的一级缓存查询的时候默认都是会先尝试从一级缓存中获取数据的,但是我们看第6行的代码做了一个判断,ms.isFlushCacheRequired(),即想每次查询都走DB也行,将<select>标签中的flushCache属性设置为true即可,这意味着每次查询的时候都会清理一遍PerpetualCache,PerpetualCache中没数据,自然只能走DB。增删改没有一级缓存。
//缓存判断查询条件是否一致:
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一级缓存。
MyBatis二级缓存的生命周期即整个应用的生命周期,应用不结束,定义的二级缓存都会存在在内存中。
从这个角度考虑,为了避免MyBatis二级缓存中数据量过大导致内存溢出,MyBatis在配置文件中给我们增加了很多配置例如size(缓存大小)、flushInterval(缓存清理时间间隔)、eviction(数据淘汰算法)来保证缓存中存储的数据不至于太过庞大。
MyBatis支持三种类型的二级缓存:
MyBatis默认的缓存,type为空,Cache为PerpetualCache
自定义缓存
第三方缓存
开启二级缓存:
存在问题:
对于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的缓存分为两种:
一级缓存,一级缓存是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(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{ ErrorContext.instance().resource(ms.getResource()).activity("executingaquery").object(ms.getId()); if(this.closed){ thrownewExecutorException("Executorwasclosed."); }else{ if(this.queryStack==0&&ms.isFlushCacheRequired()){ this.clearLocalCache(); } Listlist; 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){ Iteratori$=this.deferredLoads.iterator(); while(i$.hasNext()){ BaseExecutor.DeferredLoaddeferredLoad=(BaseExecutor.DeferredLoad)i$.next(); deferredLoad.load(); } this.deferredLoads.clear(); if(this.configuration.getLocalCacheScope()==LocalCacheScope.STATEMENT){ this.clearLocalCache(); } } returnlist; } }
一级缓存三个结论:
MyBatis的一级缓存是SqlSession级别的,但是它并不定义在SqlSessio接口的实现类DefaultSqlSession中,而是定义在DefaultSqlSession的成员变量Executor中,Executor是在openSession的时候被实例化出来的,它的默认实现为SimpleExecutorMyBatis中的一级缓存,与有没有配置无关,只要SqlSession存在,MyBastis一级缓存就存在,localCache的类型是PerpetualCache,它其实很简单,一个id属性+一个HashMap属性而已,id是一个名为"localCache"的字符串,HashMap用于存储数据,Key为CacheKey,Value为查询结果
MyBatis的一级缓存查询的时候默认都是会先尝试从一级缓存中获取数据的,但是我们看第6行的代码做了一个判断,ms.isFlushCacheRequired(),即想每次查询都走DB也行,将<select>标签中的flushCache属性设置为true即可,这意味着每次查询的时候都会清理一遍PerpetualCache,PerpetualCache中没数据,自然只能走DB。增删改没有一级缓存。
//缓存判断查询条件是否一致:
publicCacheKeycreateCacheKey(MappedStatementms,ObjectparameterObject,RowBoundsrowBounds,BoundSqlboundSql){ if(this.closed){ thrownewExecutorException("Executorwasclosed."); }else{ CacheKeycacheKey=newCacheKey(); cacheKey.update(ms.getId());//判断id属性是否相同 cacheKey.update(Integer.valueOf(rowBounds.getOffset()));//判断Offset属性是否相同 cacheKey.update(Integer.valueOf(rowBounds.getLimit()));//判断Limit属性是否相同 cacheKey.update(boundSql.getSql());//判断sql属性是否相同 ListparameterMappings=boundSql.getParameterMappings();//后面都是判断参数是否相同 TypeHandlerRegistrytypeHandlerRegistry=ms.getConfiguration().getTypeHandlerRegistry(); for(inti=0;i<parameterMappings.size();++i){ ParameterMappingparameterMapping=(ParameterMapping)parameterMappings.get(i); if(parameterMapping.getMode()!=ParameterMode.OUT){ StringpropertyName=parameterMapping.getProperty(); Objectvalue; if(boundSql.hasAdditionalParameter(propertyName)){ value=boundSql.getAdditionalParameter(propertyName); }elseif(parameterObject==null){ value=null; }elseif(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){ value=parameterObject; }else{ MetaObjectmetaObject=this.configuration.newMetaObject(parameterObject); value=metaObject.getValue(propertyName); } cacheKey.update(value); } } returncacheKey; } }
一级缓存从四组共五个条件判断两次查询是相同的:
<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一级缓存。
MyBatis二级缓存的生命周期即整个应用的生命周期,应用不结束,定义的二级缓存都会存在在内存中。
从这个角度考虑,为了避免MyBatis二级缓存中数据量过大导致内存溢出,MyBatis在配置文件中给我们增加了很多配置例如size(缓存大小)、flushInterval(缓存清理时间间隔)、eviction(数据淘汰算法)来保证缓存中存储的数据不至于太过庞大。
二级缓存:/executor/CachingExecutor.class
public<E>List<E>query(MappedStatementms,ObjectparameterObject,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{
Cachecache=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二级缓存不会存储存储过程的结果
Listlist=(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二级缓存中去
returnlist;
}
}
//优先读取以上二级缓存,query方法优先读取默认实现的一级缓存。
returnthis.delegate.query(ms,parameterObject,rowBounds,resultHandler,key,boundSql);
}
MyBatis支持三种类型的二级缓存:
MyBatis默认的缓存,type为空,Cache为PerpetualCache
自定义缓存
第三方缓存
开启二级缓存:
<settings>//mybatis.cfg.xml
<!--开启二级缓存默认值为true-->
<settingname="cacheEnabled"value="true"/>
</settings>
<mappernamespace="cn.sxt.vo.user.mapper">//mapper.xml
<!--开启本mappernamespace下的二级缓存-->
<cache></cache>
存在问题:
selecta.col1,a.col2,a.col3,b.col1,b.col2,b.col3fromtableAa,tableBbwherea.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原理(四)》 MyBatis的二级缓存的设计原理
- 深入理解mybatis原理(六) MyBatis缓存机制的设计与实现如何细粒度地控制你的MyBatis二级缓存
- 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理
- 深入理解MyBatis中的一级缓存与二级缓存
- 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理
- 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理
- 《深入理解mybatis原理(六)》 MyBatis缓存机制的设计与实现如何细粒度地控制你的MyBatis二级缓存
- Mybatis的一级缓存和二级缓存的理解和区别
- 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理
- 轻松理解MyBatis二级缓存的设计原理
- 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理
- 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理
- 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理
- 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理
- 学习笔记 1 ( mybatis 缓存理解 懒加载,一级缓存和二级缓存)
- 深入理解Mybatis二级缓存
- 《深入理解mybatis原理(六)》 MyBatis缓存机制的设计与实现如何细粒度地控制你的MyBatis二级缓存
- 深入理解mybatis原理(四) MyBatis的二级缓存的设计原理