【MyBatis源码全面解析】MyBatis一二级缓存介绍

MyBatis缓存

我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相同的查询语句,完全可以把查询结果存储起来,下次查询同样的内容的时候直接从内存中获取数据即可,这样在某些场景下可以大大提升查询效率。

MyBatis的缓存分为两种:

一级缓存,一级缓存是SqlSession级别的缓存,对于相同的查询,会从缓存中返回结果而不是查询数据库

二级缓存,二级缓存是Mapper级别的缓存,定义在Mapper文件的<cache>标签中并需要开启此缓存,多个Mapper文件可以共用一个缓存,依赖<cache-ref>标签配置

下面来详细看一下MyBatis的一二级缓存。

MyBatis一级缓存工作流程

接着看一下MyBatis一级缓存工作流程。前面说了,MyBatis的一级缓存是SqlSession级别的缓存,当openSession()的方法运行完毕或者主动调用了SqlSession的close方法,SqlSession就被回收了,一级缓存与此同时也一起被回收掉了。前面的文章有说过,在MyBatis中,无论selectOne还是selectList方法,最终都被转换为了selectList方法来执行,那么看一下SqlSession的selectList方法的实现:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
   MappedStatement ms = configuration.getMappedStatement(statement);
   return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
   throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  } finally {
   ErrorContext.instance().reset();
  }
}

继续跟踪第4行的代码,到BaseExeccutor的query方法:

 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   BoundSql boundSql = ms.getBoundSql(parameter);
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

第3行构建缓存条件CacheKey,这里涉及到怎么样条件算是和上一次查询是同一个条件的一个问题,因为同一个条件就可以返回上一次的结果回去,这部分代码留在下一部分分析。

接着看第4行的query方法的实现,代码位于CachingExecutor中:

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) {
   flushCacheIfRequired(ms);
   if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, parameterObject, boundSql);
    @SuppressWarnings("unchecked")
    List<E> list = (List<E>) tcm.getObject(cache, key);
    if (list == null) {
     list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
     tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
   }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

第3行~第16行的代码先不管,继续跟第17行的query方法,代码位于BaseExecutor中:

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 (closed) {
   throw new ExecutorException("Executor was closed.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
   clearLocalCache();
  }
  List<E> list;
  try {
   queryStack++;
   list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
   if (list != null) {
    handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
   } else {
    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
   }
  } finally {
   queryStack--;
  }
  ...
}

看12行,query的时候会尝试从localCache中去获取查询结果,如果获取到的查询结果为null,那么执行16行的代码从DB中捞数据,捞完之后会把CacheKey作为key,把查询结果作为value放到localCache中。

MyBatis一级缓存存储流程看完了,接着我们从这段代码中可以得到三个结论:

1、MyBatis的一级缓存是SqlSession级别的,但是它并不定义在SqlSessio接口的实现类DefaultSqlSession中,而是定义在DefaultSqlSession的成员变量Executor中,Executor是在openSession的时候被实例化出来的,它的默认实现为SimpleExecutor

2、MyBatis中的一级缓存,与有没有配置无关,只要SqlSession存在,MyBastis一级缓存就存在,localCache的类型是PerpetualCache,它其实很简单,一个id属性+一个HashMap属性而已,id是一个名为"localCache"的字符串,HashMap用于存储数据,Key为CacheKey,Value为查询结果

3、MyBatis的一级缓存查询的时候默认都是会先尝试从一级缓存中获取数据的,但是我们看第6行的代码做了一个判断,ms.isFlushCacheRequired(),即想每次查询都走DB也行,将<select>标签中的flushCache属性设置为true即可,这意味着每次查询的时候都会清理一遍PerpetualCache,PerpetualCache中没数据,自然只能走DB

从MyBatis一级缓存来看,它以单纯的HashMap做缓存,没有容量控制,而一次SqlSession中通常来说并不会有大量的查询操作,因此只适用于一次SqlSession,如果用到二级缓存的Mapper级别的场景,有可能缓存数据不断碰到而导致内存溢出。

还有一点,差点忘了写了,<insert>、<delete>、<update>最终都会转换为update方法,看一下BaseExecutor的update方法:

public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
   throw new ExecutorException("Executor was closed.");
  }
  clearLocalCache();
  return doUpdate(ms, parameter);
}

第6行clearLocalCache()方法,这意味着所有的增、删、改都会清空本地缓存,这和是否配置了flushCache=true是无关的。

这很好理解,因为增、删、改这三种操作都可能会导致查询出来的结果并不是原来的结果,如果增、删、改不清理缓存,那么可能导致读取出来的数据是脏数据。

一级缓存的CacheKey

接着我们看下一个问题:怎么样的查询条件算和上一次查询是一样的查询,从而返回同样的结果回去?这个问题,得从CacheKey说起。

我们先看一下CacheKey的数据结构:

public class CacheKey implements Cloneable, Serializable {

 private static final long serialVersionUID = 1146682552656046210L;

 public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();

 private static final int DEFAULT_MULTIPLYER = 37;
 private static final int DEFAULT_HASHCODE = 17;

 private int multiplier;
 private int hashcode;
 private long checksum;
 private int count;
 private List<Object> updateList;
 ...
}

其中最重要的是第14行的updateList这个两个属性,为什么这么说,因为HashMap的Key是CacheKey,而HashMap的get方法是先判断hashCode,在hashCode冲突的情况下再进行equals判断,因此最终无论如何都会进行一次equals的判断,看下equals方法的实现:

public boolean equals(Object object) {
  if (this == object) {
   return true;
  }
  if (!(object instanceof CacheKey)) {
   return false;
  }

  final CacheKey cacheKey = (CacheKey) object;

  if (hashcode != cacheKey.hashcode) {
   return false;
  }
  if (checksum != cacheKey.checksum) {
   return false;
  }
  if (count != cacheKey.count) {
   return false;
  }

  for (int i = 0; i < updateList.size(); i++) {
   Object thisObject = updateList.get(i);
   Object thatObject = cacheKey.updateList.get(i);
   if (thisObject == null) {
    if (thatObject != null) {
     return false;
    }
   } else {
    if (!thisObject.equals(thatObject)) {
     return false;
    }
   }
  }
  return true;
}

看到整个方法的流程都是围绕着updateList中的每个属性进行逐一比较,因此再进一步的,我们要看一下updateList中到底存储了什么。

关于updateList里面存储的数据我们可以看下哪里使用了updateList的add方法,然后一步一步反推回去即可。updateList中数据的添加是在doUpdate方法中:

private void doUpdate(Object object) {
  int baseHashCode = object == null ? 1 : object.hashCode();

  count++;
  checksum += baseHashCode;
  baseHashCode *= count;

  hashcode = multiplier * hashcode + baseHashCode;

  updateList.add(object);
}

它的调用方为update方法:

public void update(Object object) {
  if (object != null && object.getClass().isArray()) {
   int length = Array.getLength(object);
   for (int i = 0; i < length; i++) {
    Object element = Array.get(object, i);
    doUpdate(element);
   }
  } else {
   doUpdate(object);
  }
}

这里主要是对输入参数是数组类型进行了一次判断,是数组就遍历逐一做doUpdate,否则就直接做doUpdate。再看update方法的调用方,其实update方法的调用方有挺多处,但是这里我们要看的是Executor中的,看一下BaseExecutor中的createCacheKey方法实现:

...
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
...

到了这里应当一目了然了,MyBastis从三个维度判断两次查询是相同的:

1、<select>标签所在的Mapper的Namespace+<select>标签的id属性

2、RowBounds的offset和limit属性,RowBounds是MyBatis用于处理分页的一个类,offset默认为0,limit默认为Integer.MAX_VALUE

3、<select>标签中定义的sql语句

即只要两次查询满足以上三个条件且没有定义flushCache="true",那么第二次查询会直接从MyBatis一级缓存PerpetualCache中返回数据,而不会走DB。

MyBatis二级缓存

上面说完了MyBatis,接着看一下MyBatis二级缓存,还是从二级缓存工作流程开始。还是从DefaultSqlSession的selectList方法进去:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
   MappedStatement ms = configuration.getMappedStatement(statement);
   return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
   throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  } finally {
   ErrorContext.instance().reset();
  }
}

执行query方法,方法位于CachingExecutor中:

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   BoundSql boundSql = ms.getBoundSql(parameterObject);
   CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
   return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
 }

继续跟第4行的query方法,同样位于CachingExecutor中:

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) {
   flushCacheIfRequired(ms);
   if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, parameterObject, boundSql);
    @SuppressWarnings("unchecked")
    List<E> list = (List<E>) tcm.getObject(cache, key);
    if (list == null) {
     list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
     tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
   }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

从这里看到,执行第17行的BaseExecutor的query方法之前,会先拿Mybatis二级缓存,而BaseExecutor的query方法会优先读取MyBatis一级缓存,由此可以得出一个重要结论:假如定义了MyBatis二级缓存,那么MyBatis二级缓存读取优先级高于MyBatis一级缓存。

而第3行~第16行的逻辑:

1、第5行的方法很好理解,根据flushCache=true或者flushCache=false判断是否要清理二级缓存

2、第7行的方法是保证MyBatis二级缓存不会存储存储过程的结果

3、第9行的方法先尝试从tcm中获取查询结果,这个tcm解释一下,这又是一个装饰器模式(数数MyBatis用到了多少装饰器模式了),创建一个事物缓存TranactionalCache,持有Cache接口,Cache接口的实现类就是根据我们在Mapper文件中配置的<cache>创建的Cache实例

4、第10行~第12行,如果没有从MyBatis二级缓存中拿到数据,那么就会查一次数据库,然后放到MyBatis二级缓存中去

至于如何判定上次查询和这次查询是一次查询?由于这里的CacheKey和MyBatis一级缓存使用的是同一个CacheKey,因此它的判定条件和前文写过的MyBatis一级缓存三个维度的判定条件是一致的。

最后再来谈一点,"Cache cache = ms.getCache()"这句代码十分重要,这意味着Cache是从MappedStatement中获取到的,而MappedStatement又和每一个<insert>、<delete>、<update>、<select>绑定并在MyBatis启动的时候存入Configuration中:

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

因此MyBatis二级缓存的生命周期即整个应用的生命周期,应用不结束,定义的二级缓存都会存在在内存中。

从这个角度考虑,为了避免MyBatis二级缓存中数据量过大导致内存溢出,MyBatis在配置文件中给我们增加了很多配置例如size(缓存大小)、flushInterval(缓存清理时间间隔)、eviction(数据淘汰算法)来保证缓存中存储的数据不至于太过庞大。

MyBatis二级缓存实例化过程

接着看一下MyBatis二级缓存<cache>实例化的过程,代码位于XmlMapperBuilder的cacheElement方法中:

private void cacheElement(XNode context) throws Exception {
  if (context != null) {
   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);
  }
}

这里分别取<cache>中配置的各个属性,关注一下两个默认值:

1、type表示缓存实现,默认是PERPETUAL,根据typeAliasRegistry中注册的,PERPETUAL实际对应PerpetualCache,这和MyBatis一级缓存是一致的

2、eviction表示淘汰算法,默认是LRU算法

第3行~第11行拿到了所有属性,那么调用12行的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);
  currentCache = cache;
  return cache;
}

这里又使用了建造者模式,跟一下第16行的build()方法,在此之前该传入的参数都已经传入了CacheBuilder:

public Cache build() {
  setDefaultImplementations();
  Cache cache = newBaseCacheInstance(implementation, id);
  setCacheProperties(cache);
  // issue #352, do not apply decorators to custom caches
  if (PerpetualCache.class.equals(cache.getClass())) {
   for (Class<? extends Cache> decorator : decorators) {
    cache = newCacheDecoratorInstance(decorator, cache);
    setCacheProperties(cache);
   }
   cache = setStandardDecorators(cache);
  } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
   cache = new LoggingCache(cache);
  }
  return cache;
}

第3行的代码,构建基础的缓存,implementation指的是type配置的值,这里是默认的PerpetualCache。

第6行的代码,如果是PerpetualCache,那么继续装饰(又是装饰器模式,可以数数这几篇MyBatis源码解析的文章里面出现了多少次装饰器模式了),这里的装饰是根据eviction进行装饰,到这一步,给PerpetualCache加上了LRU的功能。

第11行的代码,继续装饰,这次MyBatis将它命名为标准装饰,setStandardDecorators方法实现为:

private Cache setStandardDecorators(Cache cache) {
  try {
   MetaObject metaCache = SystemMetaObject.forObject(cache);
   if (size != null && metaCache.hasSetter("size")) {
    metaCache.setValue("size", size);
   }
   if (clearInterval != null) {
    cache = new ScheduledCache(cache);
    ((ScheduledCache) cache).setClearInterval(clearInterval);
   }
   if (readWrite) {
    cache = new SerializedCache(cache);
   }
   cache = new LoggingCache(cache);
   cache = new SynchronizedCache(cache);
   if (blocking) {
    cache = new BlockingCache(cache);
   }
   return cache;
  } catch (Exception e) {
   throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
  }
}

这次是根据其它的配置参数来:

如果配置了flushInterval,那么继续装饰为ScheduledCache,这意味着在调用Cache的getSize、putObject、getObject、removeObject四个方法的时候都会进行一次时间判断,如果到了指定的清理缓存时间间隔,那么就会将当前缓存清空

如果readWrite=true,那么继续装饰为SerializedCache,这意味着缓存中所有存储的内存都必须实现Serializable接口

跟配置无关,将之前装饰好的Cache继续装饰为LoggingCache与SynchronizedCache,前者在getObject的时候会打印缓存命中率,后者将Cache接口中所有的方法都加了Synchronized关键字进行了同步处理

如果blocking=true,那么继续装饰为BlockingCache,这意味着针对同一个CacheKey,拿数据与放数据、删数据是互斥的,即拿数据的时候必须没有在放数据、删数据

Cache全部装饰完毕,返回,至此MyBatis二级缓存生成完毕。

最后说一下,MyBatis支持三种类型的二级缓存:

MyBatis默认的缓存,type为空,Cache为PerpetualCache

自定义缓存

第三方缓存

从build()方法来看,后两种场景的Cache,MyBatis只会将其装饰为LoggingCache,理由很简单,这些缓存的定期清除功能、淘汰过期数据功能开发者自己或者第三方缓存都已经实现好了,根本不需要依赖MyBatis本身的装饰。

以上这篇【MyBatis源码全面解析】MyBatis一二级缓存介绍就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 深入理解MyBatis中的一级缓存与二级缓存

    前言 先说缓存,合理使用缓存是优化中最常见的,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能. 一级缓存 一级缓存是SqlSession级别的缓存.在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据.不同的sqlSession之间的缓存数据区域是互相不影响的.也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的. 一级缓

  • MyBatis关于二级缓存问题

    MyBatis提供一级缓存和二级缓存,其中一级缓存是sqlSession级别的缓存,不同的sqlSession之间的缓存互不影响.二级缓存是Mapper级别的缓存,多个sqlSession操作同一个Mapper,其二级缓存是可以共享的. MyBatis有多种二级缓存方案可供选择.其中对Memcached的支持较为成熟,现以Memcached为例介绍与spring项目的集成. 使用配置 配置pom.xml,添加依赖. <dependencies> ... <dependency> &

  • MyBatis 延迟加载、一级缓存、二级缓存(详解)

    使用ORM框架我们更多的是使用其查询功能,那么查询海量数据则又离不开性能,那么这篇中我们就看下mybatis高级应用之延迟加载.一级缓存.二级缓存.使用时需要注意延迟加载必须使用resultMap,resultType不具有延迟加载功能. 一.延迟加载 延迟加载已经是老生常谈的问题,什么最大化利用数据库性能之类之类的,也懒的列举了,总是我一提到延迟加载脑子里就会想起来了Hibernate get和load的区别.OK,废话少说,直接看代码. 先来修改配置项xml. 注意,编写mybatis.xm

  • 详解Mybatis的二级缓存配置

    一个项目中肯定会存在很多共用的查询数据,对于这一部分的数据,没必要 每一个用户访问时都去查询数据库,因此配置二级缓存将是非常必要的. Mybatis的二级缓存配置相当容易,要开启二级缓存,只需要在你的Mapper 映射文件中添加一行: <cache /> 它将采用默认的行为进行缓存: 映射文件中所有的select语句将被缓存 映射文件中所有的insert.update和delete语句将刷新缓存 缓存将使用LRU(Least Recently Used)最近最少使用策略算法来回收 刷新间隔(n

  • mybatis教程之查询缓存(一级缓存二级缓存和整合ehcache)

    1 缓存的意义 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题. 2 mybatis持久层缓存 mybatis提供一级缓存和二级缓存 mybatis一级缓存是一个SqlSession级别,sqlsession只能访问自己的一级缓存的数据,二级缓存是跨sqlSession,是mapper级别的缓存,对于mapper级别的缓存不同的sqlsession是可以共享的. 3 一级缓存 3.1 原

  • mybatis二级缓存的实现代码

    二级缓存需要手动的配置和开启,具体如下 在总的配置件中设置开启二级缓存 /Mybatis02/config/mybatis-conf.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-

  • 详解Spring boot使用Redis集群替换mybatis二级缓存

    1 . pom.xml添加相关依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <!-- 依赖 --> <dependencies> &l

  • MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相同的查询语句,完全可以把查询结果存储起来,下次查询同样的内容的时候直接从内存中获取数据即可,这样在某些场景下可以大大提升查询效率. MyBatis的缓存分为两种: 一级缓存,一级缓存是SqlSession级别的缓存,对于相同的查询,会从缓存中返回结果而不是查询数据库 二级缓存,二级缓存是Mapper

  • 深入理解Mybatis二级缓存

    上篇文章给大家介绍了mybatis一级缓存,有需要的朋友可以参考下. 与一级缓存相比,二级缓存范围更大了一些,可以被多个SqlSession所共用. 同样是发送同样的查询sql会先去缓存中找,找不到再去查询数据库. 每个namespace的mapper都会有自己的一个缓存的空间. 如果两个mapper的namespace相同,执行mapper查询到的数据将存储到相同的二级缓存. 同样如果有sqlSession执行了commit 会清空二级缓存. 配置文件(不用配置也是默认开启的): 在sqlMa

  • 【MyBatis源码全面解析】MyBatis一二级缓存介绍

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相同的查询语句,完全可以把查询结果存储起来,下次查询同样的内容的时候直接从内存中获取数据即可,这样在某些场景下可以大大提升查询效率. MyBatis的缓存分为两种: 一级缓存,一级缓存是SqlSession级别的缓存,对于相同的查询,会从缓存中返回结果而不是查询数据库 二级缓存,二级缓存是Mapper

  • Spring启动流程refresh()源码深入解析

    一.Spring容器的refresh() spring  version:4.3.12  ,尚硅谷Spring注解驱动开发-源码部分 //refresh():543, AbstractApplicationContext (org.springframework.context.support) public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdo

  • LRU算法及Apache LRUMap源码实例解析

    目录 1. 什么是LRU 1.1 自定义实现LRU的要求 1.2 Apache LRUMap示例 1.2.1 pom依赖 1.2.2 demo 2. 源码解析 2.1 设计 2.2 数据结构 2.3 方法解析put get remove 2.3.1 get方法 2.3.2 remove方法 2.3.3 put方法 3. 总结 1. 什么是LRU LRU(least recently used) : 最近最少使用 LRU就是一种经典的算法,在容器中,对元素定义一个最后使用时间,当新的元素写入的时候

  • Vue源码cached解析

    目录 前言 参数解释 传入参数 返回参数 源码解释 实验解释 源码疑问 前言 创建一个纯函数的缓存版本 主要用途:优化性能——对于之前运算过一次的内容,利用闭包原理,缓存起来,避免重复调用,造成性能的浪费 /** * Create a cached version of a pure function. */ function cached (fn) { var cache = Object.create(null); return (function cachedFn (str) { var

  • Java OkHttp框架源码深入解析

    目录 1.OkHttp发起网络请求 可以通过OkHttpClient发起一个网络请求 通过Retrofit发起一个OkHttp请求 2.OkHttp的连接器 1.OkHttp发起网络请求 可以通过OkHttpClient发起一个网络请求 //创建一个Client,相当于打开一个浏览器 OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); //创建一个请求. Request request = new Request.Bui

  • Java HashTable与Collections.synchronizedMap源码深入解析

    目录 一.类继承关系图 二.HashTable介绍 三.HashTable和HashMap的对比 1.线程安全 2.插入null 3.容量 4.Hash映射 5.扩容机制 6.结构区别 四.Collections.synchronizedMap解析 1.Collections.synchronizedMap是怎么实现线程安全的 2.SynchronizedMap源码 一.类继承关系图 二.HashTable介绍 HashTable的操作几乎和HashMap一致,主要的区别在于HashTable为

  • MyBatis源码解析——获取SqlSessionFactory方式

    目录 MyBatis源码解析_获取SqlSessionFactory 首先从Resources.getResourceAsReader(path) 进入到SqlSessionFactoryBuilder.build(Reader)方法中 进入到mapperElement(XNode)方法后 如果子节点名字不是package 经过一系列的解析,终于完成了 用MyBatis的配置文件获取SqlSessionFactory MyBatis源码解析_获取SqlSessionFactory 我们都知道,在

  • Mybatis源码解析之事务管理

    目录 Mybatis事务管理 和Spring整合后的事务管理 Mybatis事务管理 我们可以在mybatis-config.xml中配置事务管理器的实现 <transactionManager type="JDBC"/> 当值为JDBC时,事务管理实现类为JdbcTransaction,底层利用数据库的Connection来管理事务 当值为MANAGED时,事务管理实现类为ManagedTransactionFactory,但它对事务的管理是一个空实现,将事务管理交给外部

  • 解析MyBatis源码实现自定义持久层框架

    目录 自定义框架设计 自定义框架实现 使用端 框架端 自定义框架设计 使用端 : 提供核⼼配置⽂件: sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml Mapper.xml : sql语句的配置⽂件信息 框架端: 1.读取配置⽂件 读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建JavaBean来存储 (1)Configuration : 存放数据库基本信息.Map<唯⼀标识,Mapper>, 唯⼀标识:namesp

  • MyBatis SqlSource源码示例解析

    目录 正文 SqlNode SqlNode接口定义 BoundSql SqlSource SqlSource解析时机 SqlSource调用时机 总结 正文 MyBatis版本:3.5.12. 本篇讲从mybatis的角度分析SqlSource.在xml中sql可能是带?的预处理语句,也可能是带$或者动态标签的动态语句,也可能是这两者的混合语句. SqlSource设计的目标就是封装xml的crud节点,使得mybatis运行过程中可以直接通过SqlSource获取xml节点中解析后的SQL.

随机推荐