MyBatis 缓存机制分析

缓存主要用来提高查询效率。以计算机的 CPU 为例,CPU 具有三级缓存,性能依次降低,优先从一级缓存查询,一级缓存未命中时再从二级缓存查询,二级缓存未命中时再从三级缓存查询。

MyBatis 缓存抽象

最简单的缓存使用 Map 即可实现,然而由于需要支持不同的使用场景,因此 MyBatis 将缓存抽象出一个 Cache 接口,定义如下:

public interface Cache {
    // 获取当前缓存的标识符
    String getId();
    // 存入缓存对象
    void putObject(Object key, Object value);
    // 获取缓存对象
    Object getObject(Object key);
    // 移除缓存对象
    Object removeObject(Object key);
    // 清空缓存
    void clear();
    // 获取缓存的对象数量
    int getSize();
    // 废弃的接口,3.2.6 版本开始不再使用
    default ReadWriteLock getReadWriteLock() {
        return null;
    }
}

可以看到 Cache 主要提供的功能就是添加、移除对象,MyBatis 会根据配置使用不同的实现,各实现具体如下:

缓存类型 特点
PerpetualCache 永久存储对象的缓存,使用 Map 实现
BlockingCache 缓存装饰器,使用 CountDownLatch 实现,支持阻塞式获取,获取不到值时当前线程会被阻塞,直到其他线程存入值
FifoCache 缓存装饰器,使用 Deque 实现,最大存储 1024 个对象,超过最大值时使用先进先出的方式移除旧对象
LoggingCache 缓存装饰器,获取对象时会打印日志,对命中率进行简单的统计
LruCache 缓存装饰器,使用 LinkedHashMap 实现,最大存储 1024 个对象,超过最大值时使用最近最少使用的方式移除旧对象
ScheduledCache 缓存装饰器,存储的对象具有一个小时的生命周期,存取或者移除对象时会将过期的对象移除
SerializedCache 缓存装饰器,存放的对象必须实现 Serializable 以支持序列化,存放的是对象序列话后的数组,存取前后会进行序列化和反序列化操作
SoftCache 缓存装饰器,使用 SoftReference 实现,以便内存不够时进行垃圾回收
SynchronizedCache 缓存装饰器,方法前添加 synchronized,每个线程需要获取到锁才能存取对象
TransactionalCache 缓存装饰器,commit 时才把对象刷新到目标 Cache 中
WeakCache 缓存装饰器,使用 WeakReference 实现,每次垃圾回收都会清空缓存对象

从上面 Cache 的表格中我们可以看到,MyBatis 使用装饰器模式定义了很多 Cache 的实现,以 LoggingCache 为例,我们看下 MyBatis 对装饰器模式的使用。

public class LoggingCache implements Cache {
    // 日志对象
    private final Log log;
    // 目标 Cache
    private final Cache delegate;
    // 请求获取对象的数量
    protected int requests = 0;
    // 命中数量
    protected int hits = 0;

    public LoggingCache(Cache delegate) {
        this.delegate = delegate;
        this.log = LogFactory.getLog(getId());
    }

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

    @Override
    public Object getObject(Object key) {
        requests++;
        final Object value = delegate.getObject(key);
        if (value != null) {
            hits++;
        }
        if (log.isDebugEnabled()) {
            log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
        }
        return value;
    }
    
    // 获取命中率
    private double getHitRatio() {
        return (double) hits / (double) requests;
    }
	
	... 省略部分方法
}

LoggingCache 类对目标 Cache 进行装饰,持有目标 Cache 的引用,当调用 Cache 接口方法时委托给目标 Cache 处理,LoggingCache 类对#getObject方法进行增强,记录了请求次数,命中次数,并且打印了日志。其他装饰器的实现和 LoggingCache 类似。

如何在 MyBatis 中配置缓存

 

上一篇:那些年我们一起追过的缓存写法(三)


下一篇:springboot整合spring @Cache和Redis