详解Mybatis的缓存
mybatis的缓存
mybatis是一个查询数据库的封装框架,主要是封装提供灵活的增删改sql,开发中,service层能够通过mybatis组件查询和修改数据库中表的数据;作为查询工具,mybatis有使用缓存,这里讲一下mybatis的缓存相关源码。
缓存
在计算机里面,任何信息都有源头,缓存一般指源头信息读取后,放在内存或者其他读取较快的地方,下次读取相同信息不去源头查询而是直接从内存(或者能快速存取的硬件)读取。这样可以减少硬件使用,提高读取速度。
mybatis也是这样,查询数据库的数据之后,mybatis可以把查询结果缓存到内存,下次查询如果查询语句相同,并且查询相关的表的数据没被修改过,就可以直接返回缓存中的结果,而不用去查询数据库的语句,有效节省了时间。
简单看一下mybatis一级缓存和二级缓存相关源码,学习使用
一级缓存
通过查看源码可知,一级缓存是绑定sqssession中的,所以每次查询sqlsession不同就失效,相同的sqlsession可以使用一级缓存。
mybatis默认sqlsession:org.apache.ibatis.session.defaults.defaultsqlsession
构造方法中传入executor(查询执行对象)
public defaultsqlsession(configuration configuration, executor executor, boolean autocommit) { this.configuration = configuration; this.executor = executor; this.dirty = false; this.autocommit = autocommit; }
executor中携带一级缓存成员:
protected baseexecutor(configuration configuration, transaction transaction) { this.transaction = transaction; this.deferredloads = new concurrentlinkedqueue<>(); this.localcache = new perpetualcache("localcache"); //默认一级缓存 this.localoutputparametercache = new perpetualcache("localoutputparametercache"); this.closed = false; this.configuration = configuration; this.wrapper = this; }
查询使用一级缓存逻辑
org.apache.ibatis.executor.baseexecutor.query()
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()); list<e> list; try { querystack++; //localcache 一级缓存 list = resulthandler == null ? (list<e>) localcache.getobject(key) : null; //先从一级缓存中获取,key是通过sql语句生成 if (list != null) { handlelocallycachedoutputparameters(ms, key, parameter, boundsql); } else { // 如果缓存中没有 才从数据库查询 list = queryfromdatabase(ms, parameter, rowbounds, resulthandler, key, boundsql); } } finally { querystack--; } return list; } //从数据库读取数据 private <e> list<e> queryfromdatabase(mappedstatement ms, object parameter, rowbounds rowbounds, resulthandler resulthandler, cachekey key, boundsql boundsql) throws sqlexception { list<e> list; localcache.putobject(key, execution_placeholder); try { list = doquery(ms, parameter, rowbounds, resulthandler, boundsql); } finally { localcache.removeobject(key);//将一级缓存清除 } localcache.putobject(key, list);//返回查询结果之前,先放入一级缓存 刷新 if (ms.getstatementtype() == statementtype.callable) { localoutputparametercache.putobject(key, parameter); } return list; }
二级缓存
二级缓存mapper中的,默认是开启的,但需要在映射文件mapper.xml中添加<cache/>标签
<mapper namespace="usermapper"> <cache/><!-- 添加cache标签表示此mapper使用二级缓存 --> </mapper>
配置false可以关闭二级缓存
二级缓存的解析
org.apache.ibatis.builder.xml.xmlmapperbuilder
private void configurationelement(xnode context) { try { //... cacheelement(context.evalnode("cache")); //解析cache标签 } catch (exception e) { throw new builderexception("error parsing mapper xml. the xml location is '" + resource + "'. cause: " + e, e); } } private void cacheelement(xnode context) { if (context != null) { // if hava cache tag 如果有cache标签才执行下面的逻辑 string type = context.getstringattribute("type", "perpetual"); class<? extends cache> typeclass = typealiasregistry.resolvealias(type); string eviction = context.getstringattribute("eviction", "lru"); class<? extends cache> evictionclass = typealiasregistry.resolvealias(eviction); long flushinterval = context.getlongattribute("flushinterval"); integer size = context.getintattribute("size"); boolean readwrite = !context.getbooleanattribute("readonly", false); boolean blocking = context.getbooleanattribute("blocking", false); properties props = context.getchildrenasproperties(); builderassistant.usenewcache(typeclass, evictionclass, flushinterval, size, readwrite, blocking, props);//建立二级缓存 } }
org.apache.ibatis.builder.mapperbuilderassistant.usenewcache():
public cache usenewcache(class<? extends cache> typeclass, class<? extends cache> evictionclass, long flushinterval, integer size, boolean readwrite, boolean blocking, properties props) { cache cache = new cachebuilder(currentnamespace) .implementation(valueordefault(typeclass, perpetualcache.class)) .adddecorator(valueordefault(evictionclass, lrucache.class)) .clearinterval(flushinterval) .size(size) .readwrite(readwrite) .blocking(blocking) .properties(props) .build(); configuration.addcache(cache);//二级缓存赋值,如果cache标签为空,不会执行此方法,currentcache为空 currentcache = cache; return cache; }
在映射文件mapper中如果没有cache标签,不会执行上面的usenewcache方法,cache为null,就不会使用二级缓存(相当于失效)。
查询使用二级缓存逻辑
org.apache.ibatis.executor.cachingexecutor :
@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) {//如果二级缓存对象不为空 尝试在二级缓存中获取(没有cache标签此对象就是空) flushcacheifrequired(ms); if (ms.isusecache() && resulthandler == null) { ensurenooutparams(ms, boundsql); @suppresswarnings("unchecked") list<e> list = (list<e>) tcm.getobject(cache, key); //从二级缓存中获取数据 if (list == null) { list = delegate.query(ms, parameterobject, rowbounds, resulthandler, key, boundsql); //如果为空,使用delegate查询(baseexecutor) tcm.putobject(cache, key, list); // 查询结果保存到二级缓存 } return list; } } return delegate.query(ms, parameterobject, rowbounds, resulthandler, key, boundsql); }
二级缓存和一级缓存不用想,数据库的数据被修改是要清空缓存的,不然数据有误,至于怎么清空,是另一套逻辑了,mapper中的cache标签可以配置一些参数,比如缓存定期清空。
一级二级缓存先后顺序
mybatis默认是先查询二级缓存,没有,再查看一级缓存,都为空,最后查询数据库
以上就是详解mybatis的缓存的详细内容,更多关于mybatis的缓存的资料请关注其它相关文章!