上一篇整合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. data_model_action

    w PowerDesigner

  2. JS-Promise(使异步操作同步执行)

    单个异步操作同步 <div id="box"></div> <script> var box = document.querySelector( ...

  3. springcloud核心组件Eureka、Ribbon、Feign、Hystrix、Zuul

    各个组件角色扮演:    Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取 ...

  4. vue-安装及新建一个项目

    1.首先我们需要安装node.js,下载地址是:https://nodejs.org/en/ 之后是node.js的正常安装步骤: 接着打开window+R输入cmd回车进入命令行模块 2.确认nod ...

  5. 【ABAP系列】SAP 后台JOB如何DEBUG

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP 后台JOB如何DEBUG ...

  6. 手撸红黑树-Red-Black Tree 入门

    一.学习红黑树前的准备: 熟悉基础数据结构 了解二叉树概念 二.红黑树的规则和规则分析: 根节点是黑色的 所有叶子节点(Null)是黑色的,一般会认定节点下空节点全部为黑色 如果节点为红色,那么子节点 ...

  7. Learning OSG programing---Multi Camera in one window 在单窗口中创建多相机

    在学习OSG提供的例子osgCamera中,由于例子很长,涉及很多细节,考虑将其分解为几个小例子.本文介绍实现在一个窗口中添加多个相机的功能. 此函数接受一个Viewer引用类型参数,设置图形上下文的 ...

  8. jmeter uniq 取值方式设置

  9. 使用bootstrap制作网站导航

    除了制作选项卡和下拉菜单,bootstrap还能编写出美观的网站导航栏 一.仿知乎导航栏 <body> <nav class="navbar navbar-default ...

  10. 难倒你了吧!ArrayList 为啥要实现 RandomAccess 接口?

    作者:蔡先森_caiyq https://www.jianshu.com/p/3e2a9e4c9e01 在我们的开发中,List接口是最常见不过,而且我们几乎每天都在用ArrayList或者Linke ...