• MyBatis 二级缓存


    MyBatis 二级缓存

    1 二级缓存是什么?

    二级缓存是 MyBatis 中的一个重要的概念。

    • 二级缓存是存储在 MappedStatement 中的成员变量 Cache
    • 默认情况下,Cache 实例对象最底层是 PerpetualCache,但是底层之上还装饰了一层层的其他功能的 Cache。
    package org.apache.ibatis.mapping;
    
    public final class MappedStatement {
        private Cache cache;
    }
    

    Cache 接口很简洁,主要就是 put/get/remove/clear 这些方法

    • 配置类 Configuration 中有一个 Map 管理 所有 MappedStatement 的二级缓存,主键是命名空间,即 包名+Mapper接口类名
    package org.apache.ibatis.session;
    public class Configuration {
        protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
    }
    

    2 怎样设置打开/关闭 二级缓存:

    如果你从来没有接触过 Mybatis , 建议你移步 传送门

    2.1 全局关闭(针对应用关闭二级缓存)

    第一种方式:通过 Java 代码设置

    package org.apache.ibatis.session;
    public class Configuration {
        // 是否开启二级缓存
        protected boolean cacheEnabled = true;
        public void setCacheEnabled(boolean cacheEnabled) {
            this.cacheEnabled = cacheEnabled;
        }
    }
    

    第二种方式:通过 xml 文件配置

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
     "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <properties></properties>
        <!-- 该配置影响的所有映射器中配置的缓存的全局开关。默认true -->
        <!-- settings 需要放在 properties 后面, 不然会报错-->
        <settings>
            <setting name="cacheEnabled" value="false"/>
        </settings>
    </configuration>
    

    cacheEnabled 是如何生效的?

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        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 这个类
        if (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }
    

    我们开篇就提过二级缓存其实就是使用 MappedStatement 的成员变量 Cache,如果我们都不创建 CacheExecutor 了,自然也就不会开启二级缓存了。

    2.2 局部关闭(针对 mappedStatement 关闭缓存)

    第一种方式:通过 Java 代码设置

    @CacheNamespace
    public interface UserMapper {
        @Select({" select * from users where id=#{1}"})
        @Options(useCache = true)
        User selectByid(Integer id);
    }
    

    第二种方式:通过 xml 文件配置

    <mapper namespace="cn.skilled.peon.mybatis.UserMapper">
    
        <select id="selectByid2" useCache="true" resultType="cn.skilled.peon.mybatis.beans.User">
            select * from users where id=#{arg0}
        </select>
    </mapper>
    

    useCache 只是一个控制缓存的标记

    2.3 (cacheEnabled设为false) ≠ (cache == null)

    你课不要天真的以为设置了 cacheEnabled=false,就不会创建 Cache 对象了!
    也不要以为设置了 cacheEnabled=true,就会自动全局创建缓存了。
    以下就是我开启了二级缓存,却在调用 configuration.getCache(UserMapper.class) 时发生的异常:

    Caches collection does not contain value for cn.skilled.peon.mybatis.UserMapper

    这个报错发生在 Configuration 的内部类 StrictMap 的 get() 方法中。

    3. 给 mappedStatment 创建 Cache

    3.1 第一种方式:使用 xml 配置:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="cn.skilled.peon.mybatis.UserMapper">
        <!--在此处增加 cache 标签创建二级缓存 -->
        <cache />
        ...
    </mapper>
    

    <cache/> 如何生效的

    • 根据 mapper.xml 中 cache 标签内的属性 来创建 Cache。这里其实创建的是Cache责任链,这个稍后会讲。

    • 通过 CacheBuilder 创建 Cache 完成后,再通过 addCache 把二级缓存的引用交给 Configuration 管理。

    • currentCache 会在 Mapper 创建完成后,通过构造器模式 MappedStatement.Builder 设置到 MappedStatement 对象的 cache 中

    3.2 第二种方式:使用 @CacheNamespace

    @CacheNamespace
    public interface UserMapper {}
    

    @CacheNamespace 注解如何生效

    • 在 MapperAnnotationBuilder 中的 parseCache 方法解析缓存

    • 殊途同归,最终也还是调用了 MapperBuilderAssistant 来创建 Cache

    4.Cache责任链

    • “俄罗斯套娃”

    4.1 Cache 责任链是如何构成?--CacheBuilder

    package org.apache.ibatis.mapping;
    
    public class CacheBuilder {
        private final String id; // =命名空间=包名+接口类名
        private Class<? extends Cache> implementation;
        private final List<Class<? extends Cache>> decorators;
        private Integer size;
        private Long clearInterval;
        private boolean readWrite;
        private Properties properties;
        private boolean blocking;
    
        public Cache build() {
            setDefaultImplementations();
            // implementation 代表的是最底层的Cache
            // 可以用 磁盘,内存,或者第三方工具
            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;
        }
        
        private void setDefaultImplementations() {
            if (implementation == null) {
                // 如果没有设置最底层的实现类,那就用 内存缓存 作为 二级缓存
                implementation = PerpetualCache.class;
                // 如果没有设置特别的淘汰策略,就默认使用 LRU 最近最少使用算法 来淘汰内存
                if (decorators.isEmpty()) {
                    decorators.add(LruCache.class);
                }
            }
        }
    }
    

    4.2 责任链模式

    • 责任链条上的每一个实例对象,分别负责一部分工作。且 MyBatis 二级缓存的责任链是顺序固定的。

    5.二级缓存-事务

    CachingExecutor 中还有一个重要的成员变量就是 TransactionalCacheManager
    主要方法就是 rollback、commit

    5.1 事务缓存管理器-TransactionalCacheManager

    package org.apache.ibatis.cache;
    
    public class TransactionalCacheManager {
        private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
        private TransactionalCache getTransactionalCache(Cache cache) {
            TransactionalCache txCache = transactionalCaches.get(cache);
            if (txCache == null) {
                txCache = new TransactionalCache(cache);
                transactionalCaches.put(cache, txCache);
            }
            return txCache;
        }
    }
    

    通过 事务管理器 tcm 调用

    • 设置缓存putObject、
    • 获取缓存getObject、
    • 清除缓存 clear
      都先会用 getTransactionalCache 方法,会在原来的 Cache 上面再装饰一层 TransactionalCache。

    这样在事务提交 commit /回滚 rollback 时,再把所有的缓存执行一遍。

    5.2 TransactionalCache

    以 putObject 方法为例,所有操作会先作用在 TransactionalCache,当 commit 时才能真正生效!

    package org.apache.ibatis.cache.decorators;
    public class TransactionalCache implements Cache {
        private final Map<Object, Object> entriesToAddOnCommit;
        
        @Override
        public void putObject(Object key, Object object) {
            // 注意,新提交的缓存对象,会先保存在 事务暂存区(TransactionalCache 的 HashMap) 中
            entriesToAddOnCommit.put(key, object);
        }
        
        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);
                }
            }
        }
    }
    

    我知道光看这段代码还是不知道这个事务暂存区是干嘛的,接着往下看

    5.3 事务暂存区

    1. 只要你用到选择开启了enableEnabled==true(默认关闭的),那你的代码就一定会走到 CachingExecutor 中,继而再传递给 BaseExecutor,默认情况是 ReuseExecutor
    2. 因为你创建了 CachingExecutor ,那么你也必然会拥有 TransactionCacheManager 实例,代码也必然会走到 TransactionCacheManager 的 查改方法中。
    3. useCache == true 将具体决定你在执行 query 方法时,能否使用二级缓存来快速返回结果,而不必查询数据库。默认就是 true。

    5.4 延迟执行

    • clear 立刻修改 TransactionalCache 中的标志位 clearOnCommit = true,并且 清除原本要提交的对象集合 entriesToAddOnCommit。真正清除二级缓存的操作发生在 提交事务时。
    • putObject 也只是先把对象放入 entriesToAddOnCommit,等到 提交事务时,才真正添加到二级缓存。
    • 总结一下:在一个事务中,用户的所有操作,仅针对 TransactionalCache,只有 commit 之后,才会作用于 底层的 PerpetualCache
  • 相关阅读:
    求一个二维数组的最大子矩阵(王伟光,曹锦锋)
    第二个冲刺周期第2天
    软件——第二个冲刺周期
    我的软件创意——历史上的今天
    我的ideas之网络安全——基于NABC模型
    电梯调度 最终版
    敏捷软件开发方法——scrum
    求一个二维数组中 子数组和的最大值
    电梯调度算法(二)
    结对项目开发-电梯调度(大体设计思路)
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/mybatis-cache-executor-and-transaction-cache-manager.html
Copyright © 2020-2023  润新知