zoukankan      html  css  js  c++  java
  • mybatis源码分析之06二级缓存

    上一篇整合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集合中缓存起来的数据写入到二级缓存中

  • 相关阅读:
    DRF版本控制
    Django Rest Framework 视图和路由
    ModelSerializer
    linux下jdk安装与配置
    linux下各种安装包下载地址
    Creating mailbox file: 文件已存在
    vim常用设置
    zookeeper集群搭建与升级
    linux下shell 脚本 中windows换行符换成linux换行符
    spring注解
  • 原文地址:https://www.cnblogs.com/z-qinfeng/p/11924755.html
Copyright © 2011-2022 走看看