上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的。

通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置

<cache eviction="LRU" type="qinfeng.zheng.RedisCache"/>

MYBATIS源码分析之02配置文件解析  这篇文章讲解了mybatis解析配置文件具体的过程。

这里再总结一下,mybatis解析主配置文件使用了XMLConfigBuilder对象;解析具体的Mapper接口配置则使用了XMLMapperBuilder对象,是不是很好记忆。。。

所以,要了解mybatis是如何是解析  <cache eviction="LRU" type="qinfeng.zheng.RedisCache"/>  这行代码 ,肯定得去XMLMapperBuilder这个类了,

具体看XMLMapperBuilder#configurationElement(XNode  context)方法。

很明显,具体的实现在cacheElement()方法中, 结合debug看一下

接着我们可以再深究一下builderAssistant是如何创建这个缓存的。

  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;
}

由上面的这段代码知道 ,mybatis框架使用了一个构建者模式去创建一个Cache对象。 对于复杂对象的创建,我认为使用构建者模式是很有必要的,代码起来也很会舒服!

我们看看build()方法中,具体是如何构造这个Cache对象的。

  public Cache build() {
setDefaultImplementations();
// 使用反射创建了一个Cache对象(真实类型就是我们的RedisCache)
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;
}

至此,应该已经说明了mybatis是如何解析二级缓存的配置的了!

***************************************************************************************************************

以下内容,我们看看mybatis是如何使用二级缓存的。

在说mybatis的一级缓存时,提到了二级缓存,就是CachingExecutor#query()方法,具体见下面这段代码

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

咱们还是debug分析

哈哈,这个tcm就跟二级缓存密切相关了,我们需要细读, 从getObject(cache,key)方法开始,我们会进入到TransactionalCache#getObject(Object key)方法。

F7再进入到LoggingCache#getObject(key)

哈哈,这里的delegate就是RedisCache了,getObject(key)就会调用我们自定义的逻辑了,也就是下面这段代码,从redis中去拿数据

   @Override
public Object getObject(Object key) {
return execute(jedis -> {
try {
byte[] bytes = jedis.hget(id.getBytes(), key.toString().getBytes());
if (bytes == null) {
return null;
}
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
return objectInputStream.readObject();
} catch (Exception e) {
throw new RuntimeException("get data from redis error!", e);
}
});
}

至此,从二级缓存中getObject已经说完了,下面说下putObject, 我们还是从TransactionalCache#putObject(key,value)说起。 大家可以知道 ,mybatis的二级缓存与TransactionalCache脱离不了关系,mybatis的一级缓存与PerpetualCache 脱离不了关系。

好,言归正传,继续看TransactionalCache#putObject(key,value)方法

 @Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}

大家可以看到,putObject方法只是将数据put到entriesToAddOnCommit这个Map集合中,并没有真定写入到redis中。

上篇已经说了,mybatis框架是在commit()时,将entriesToAddOnCommit中的数据写入到redis中, 我们可以简单看一下源码。

  public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
  private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}

好,完了,下面总结一下!

总结;

(1) 如果MappedStatement中没有二级缓存,直接走一级缓存

(2) 如果MappedStatement中有二级缓存,则从二级缓存中查询数据

(3) 如果二级缓存中没有查询到数据,则走一级缓存(一级没有,就查库), 并且将从一级缓存中获取到的数据写入到TransactionalCache#entriesToAddOnCommit集合中,缓存起来

(4) 调用SqlSession#commit()方法时,会将TransactionalCache#entriesToAddOnCommit集合中缓存起来的数据写入到二级缓存中

mybatis源码分析之06二级缓存的更多相关文章

  1. Mybatis源码分析之Cache二级缓存原理 (五)

    一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...

  2. mybatis源码分析之05一级缓存

    首先需要明白,mybatis的一级缓存就是指SqlSession缓存,Map缓存! 通过前面的源码分析知道mybatis框架默认使用的是DefaultSqlSession,它是由DefaultSqlS ...

  3. mybatis源码分析(7)-----缓存Cache(一级缓存,二级缓存)

    写在前面  MyBatis 提供查询缓存,用于减轻数据库压力,提高数据库性能. MyBatis缓存分为一级缓存和二级缓存. 通过对于Executor 的设计.也可以发现MyBatis的缓存机制(采用模 ...

  4. Mybatis源码分析之Cache一级缓存原理(四)

    之前的文章我已经基本讲解到了SqlSessionFactory.SqlSession.Excutor以及Mpper执行SQL过程,下面我来了解下myabtis的缓存, 它的缓存分为一级缓存和二级缓存, ...

  5. MyBatis源码分析(五):MyBatis Cache分析

    一.Mybatis缓存介绍 在Mybatis中,它提供了一级缓存和二级缓存,默认的情况下只开启一级缓存,所以默认情况下是开启了缓存的,除非明确指定不开缓存功能.使用缓存的目的就是把数据保存在内存中,是 ...

  6. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  7. MyBatis源码分析(4)—— Cache构建以及应用

    @(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...

  8. Mybatis源码分析-BaseExecutor

    根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...

  9. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

随机推荐

  1. 126B Password[扩展kmp学习]

    题目大意 给你一个字符串,求它的一个子串使得这个子串即使前缀又是后缀又出现在不是前缀且不是后缀的地方 分析 扩展kmp就是定义z[i]表示i~n的子串与整个串的最长公共前缀的长度是z[i] 所以这个题 ...

  2. 124、TensorFlow替换函数

    # tf.device给你了很多可伸缩性在TensorFlow的计算图中选择放置你的单独的操作 # 在许多的情况下,有很多启发可以工作的很好 # 例如tf.train.replica_device_s ...

  3. ngrinder压力测试

    文章目录 另一篇 部署demo 写脚本 压力测试 目标主机监控 可能报错 总结: 另一篇 https://blog.csdn.net/dataiyangu/article/details/888518 ...

  4. canvas绘制随机颜色的柱形图

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. 推荐使用MarkdownPad2进行Markdown写作

    笔者更推荐使用notepad++写markdown Atom也有Bug,还是Visual Studio Code好用. 去官网下载MarkdownPad2的安装包,并安装之. 如果你是Windows ...

  6. Run Your Tensorflow Deep Learning Models on Google AI

    People commonly tend to put much effort on hyperparameter tuning and training while using Tensoflow& ...

  7. 报错:Uncaught SyntaxError: Unexpected token)

    用JSON格式传值时,js一直 报这个错误:Uncaught SyntaxError: Unexpected token) 错误位置是:result=eval('('+result+')'): 原因: ...

  8. 批量更新:A表数据源 B表目标

    update a set a.Title = b.Title from Table_A a ,Tbale_B b where a.ID_Table_B = b.ID

  9. docker实现分布式项目部署

    docker的安装及基本命令这里就略过了,可以看我的这篇笔记https://www.cnblogs.com/pyweb/p/11351878.html. 这次需要在docker上部署两个项目,整体的流 ...

  10. Mac apache You don't have permission to access / on this server.

    在mac下配置完apache和php环境后,通过localhost访问页面,出现403Forbidden.页面提示: Forbidden You don't have permission to ac ...