• mybatis二级缓存原理


    在mybatis中,一级缓存是默认开启的,无法关闭;二级缓存可以选择开启或者关闭,这篇博客主要介绍二级缓存的原理,具体的使用,不再介绍了

    解析

    二级缓存在使用的时候,需要先进行解析,对二级缓存的处理,主要体现在几个方面

    1. 对mapper.xml文件的解析,在解析时,会解析节点
    2. 初始化executor对象时,会判断是否开启二级缓存
    3. 在sql执行时,会使用到二级缓存
    解析节点

    org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement

    这是解析节点的方法,至于怎么调用过来的,可以看下前面mybatis源码的博客,有介绍过
    这里解析的是mapper.xml中的节点

    private void cacheElement(XNode context) {
        if (context != null) {
          String type = context.getStringAttribute("type", "PERPETUAL");
          //这里是根据type从存放别名的map中获取对应的class类
          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);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可以看到,这个方法中,是解析节点中各个属性的配置,在解析出来配置的属性之后,会通过builderAssistant.useNewCache()这个方法,把所有属性聚合到一个cache对象中
    在useNewCache方法中,会通过

    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这么一段代码,初始化一个cache对象,所以我们需要注重看的是这里的cache对象是怎么初始化的,我们来看下其build()方法

    public Cache build() {
       // 设置默认的缓存实现类
       setDefaultImplementations();
       Cache cache = newBaseCacheInstance(implementation, id);
       setCacheProperties(cache);
       // issue #352, do not apply decorators to custom caches
       // 判断是否是PerpetualCache
       if (PerpetualCache.class.equals(cache.getClass())) {
         for (Class<? extends Cache> decorator : decorators) {
           cache = newCacheDecoratorInstance(decorator, cache);
           setCacheProperties(cache);
         }
         // 在这里,会开始包装二级缓存需要使用的cache
         cache = setStandardDecorators(cache);
       } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
         cache = new LoggingCache(cache);
       }
       return cache;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这个方法中,简单看下逻辑:先设置默认的缓存实现类,然后判断当前的implementation是否是PerpetualCache;如果是,核心逻辑在

    cache = setStandardDecorators(cache);

    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);
          }
          /**
           * 在lruCache基础之上,包装了一层SerializedCache
           */
          if (readWrite) {
            cache = new SerializedCache(cache);
          }
          /**
           * 在SerializedCache基础之上,包装了一层LoggingCache
           */
          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);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    可以看到,这里在初始化cache对象的时候,会一层套一层,各种cache对象,所以,我们可以理解为,最后我们返回的是BlockingCache(如果开启了blocking),但是这个对象的内部,会套了多层cache

    初始化executor对象

    为什么会说到这个点呢?因为我们知道,如果开启了二级缓存,会先调用org.apache.ibatis.executor.CachingExecutor#query()这个方法,如果二级缓存中,没有获取到结果,会调用到org.apache.ibatis.executor.BaseExecutor#query()方法,所以,我们可以认为CachingExecutor是二级缓存的处理逻辑
    那为什么二级缓存开启了,会先调用CachingExecutor呢?就要看初始化executor的逻辑

    org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()
    	org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
    		org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
    
    • 1
    • 2
    • 3
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        // 默认的executor是SimpleExecutor
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
          executor = new SimpleExecutor(this, transaction);
        }
        //如果二级缓存开启,创建cachingExecutor,这里的executor会被包装在cachingExecutor中; 这里的cacheEnabled,是在解析全局配置文件时,解析节点时,赋值的,如果设置了cacheEnable为true,这里的属性值就会是true
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        /**
         * 上面的逻辑是根据不同的executorType类型来初始化不同的执行器
         * 下面是为executor生成动态代理对象
         */
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这个方法,我们可以看到,在初始化executor对象时,如果开启了二级缓存,会在executor外面再包装一层CachingExecutor;所以,这也是 为什么在sql执行的时候,会先调用到CachingExecutor的query()方法

    调用

    在sql执行时,会通过sqlSession的selectList/selectOne来完成,在内部,会调用executor的query()方法,所以对于开启了二级缓存的场景下,会先调用CachingExecutor的query()方法

    @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是mybatis的二级缓存。如果二级缓存不为null,并且二级缓存开启了,就从二级缓存中获取数据,否则,就从一级缓存中取数据,如果一级缓存中
           * 也没有数据,就从数据库中获取,并将查询结果存入到一级缓存和二级缓存中
           *
           * flushCacheIfRequired():是指xml中,select、update、delete、insert节点的flushCache属性,默认:insert、delete、update是true,select是false(这里的默认值是在解析xml文件的时候,添加的)
           */
          flushCacheIfRequired(ms);
            /**
             * 这里对应的是useCache属性;该属性值:如果是select标签,是true,非select标签为false
             * mapper.xml文件中  标签的属性
             */
          if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            /**
             * 下面是真正从二级缓存中查询数据,是通过责任链 + 装饰器模式实现的
             * 类似于套娃的形式,一层套一层
             */
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                /**
                 * 调用一级缓存;
                 * 从一级缓存中查到数据之后,调用tcm的put方法,这里的put是把对应的对象,放到了一个中间变量map集合中,这样做,是为了防止脏数据
                 * 只有在事务提交的时候,才会真正的把数据写入到二级缓存
                 */
              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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    这里的注释也比较明白,简单来说,就是先从二级缓存中查询,如果二级缓存可以查到,就返回,查不到,就去一级缓存中查询,所谓的一级缓存,是BaseExecutor来处理的,我们先看下从二级缓存获取的逻辑:
    在这里插入图片描述
    这里可以看到,就是上面解析节点时,包装的层数,现在在使用的时候,会一层一层的调用,最后实际是存储在PerpetualCache中

  • 相关阅读:
    未来5年,只有这种产品团队才能开启上帝视角【玩转IPD】
    ubuntu 20.04 + Anaconda + cuda-11.8 + opencv-4.8.0(cuda)
    Pjax 下动态加载插件方案
    Linux 遍历目录(cd 命令)
    《C语言程序设计》第4版 何钦铭、颜晖主编 课后习题答案 第12章 课后习题
    核对不同文件夹所含内容的差异并提取缺失内容:Python代码
    苹果电脑如何优化MAC系统内存?怎么清理系统垃圾?
    Java检测网络是否正常通讯
    Promrtheus+Grafana+onealert--实现报警
    Django框架简单搭建增删改查页面 Django请求生命周期流程图
  • 原文地址:https://blog.csdn.net/CPLASF_/article/details/126565824