欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Mybatis的缓存

程序员文章站 2022-03-29 10:19:43
mybatis的缓存mybatis是一个查询数据库的封装框架,主要是封装提供灵活的增删改sql,开发中,service层能够通过mybatis组件查询和修改数据库中表的数据;作为查询工具,mybati...

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的缓存的资料请关注其它相关文章!

相关标签: Mybatis 缓存