频繁的数据库操做是很是耗费性能的(主要是由于对于DB而言,数据是持久化在磁盘中的,所以查询操做须要经过IO,IO操做速度相比内存操做速度慢了好几个量级),尤为是对于一些相同的查询语句,彻底能够把查询结果存储起来,下次查询一样的内容的时候直接从内存中获取数据便可,这样在某些场景下能够大大提高查询效率。算法
SqlSession>DefaultSqlSession(selectList)>this.executor.query>Executor(一级缓存BaseExecutor, 二级缓存CachingExecutor)sql
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; } }
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; } }
即只要两次查询知足以上三个条件且没有定义flushCache="true",那么第二次查询会直接从MyBatis一级缓存PerpetualCache中返回数据,而不会走DB。缓存
MyBatis二级缓存的生命周期即整个应用的生命周期,应用不结束,定义的二级缓存都会存在在内存中。mybatis
从这个角度考虑,为了不MyBatis二级缓存中数据量过大致使内存溢出,MyBatis在配置文件中给咱们增长了不少配置例如size(缓存大小)、flushInterval(缓存清理时间间隔)、eviction(数据淘汰算法)来保证缓存中存储的数据不至于太过庞大。app
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支持三种类型的二级缓存:性能
<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,即它们属于两个命名空间,若是此时启用缓存:ui
此时问题就来了,即便第(2)步tableB更新了col1与col2两个字段,第(3)步MapperA走二级缓存查询到的这6个字段依然是原来的这6个字段的值,由于咱们从CacheKey的3组条件来看:this
对于MapperA来讲,其中的任何一个条件都没有变化,天然会将原结果返回。spa
这个问题对于MyBatis的二级缓存来讲是一个无解的问题,所以使用MyBatis二级缓存有一个前提:
必须保证全部的增删改查都在同一个命名空间下才行。