myBaits的缓存机制

myBatis缓存综述

为了避免相同的查询重复去数据库中取值,myBatis形成了两级缓存机制,先从第二级缓存中获取,获取不到时从第一级缓存中获取,仍然取不到时才会实际的去库中查询。其中二级缓存是SqlSessionFactory级别的,所有由同一个工厂构建出来的SqlSession共享缓存,而一级缓存是SqlSession级别的,每个SqlSession独享缓存。在实现上,myBatis以Cache接口为核心、以PerpetualCache为基本实现,构建了多个单一职能的缓存组件,这些组件,有的实现了缓存结构满时,旧缓存的替换,比如最近最少使用算法的LruCache、先进先出的FifoCache等;有的实现了在存取缓存时打出日志,比如LoggingCache;还有的能周期性的对缓存进行清理,如ScheduledCache等。最终以装饰器模式进行组合复用,形成了多层次的缓存体系。

缓存名称特性依赖的数据结构
PerpetualCache底层cacheHashMap
FifoCache先进先出替换Deque
LruCache最近最少使用的被替换LinkedHashMap
ScheduledCache周期性的清除缓存 
BlockingCache ConcurrentHashMap<Object, ReentrantLock>
LoggingCache打日志Log
SerializedCache将缓存的值序列化存储 
SynchronizedCache所有的方法加入了synchronized 
SoftCache ReferenceQueue、SoftReference
WeakCache ReferenceQueue、WeakReference
TransactionalCache  

 

一级缓存的实现

一级缓存是默认开启的,无需任务配置。在实现方式上,是直接在构造Executor对象时生成名为localCache的PerpetualCache缓存,没有任何形式的装饰和组合,其底层维护一个HashMap存储cache。

一级缓存的存值时机,是在BaseExecutor的queryFromDatabase方法中完成的:

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
			ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

取值时机是在BaseExecutor的query方法中。

二级缓存的配置与实现

二级缓存是默认不开启的,需要在mapper.xml中或者@CacheNamespace注解显式的进行设置,同一个SqlSessionFactory构建的SqlSession共享缓存,和SQL语句的解析一样,在初始化阶段就将缓存的配置一起解析,最终将缓存对象放置到Configuration对象中。

配置的例子:

<mapper namespace="com.mybatis.read.CountryDao">
	<cache eviction="LRU" readOnly="true" flushInterval="100000" size="1024"></cache>
    <select id="queryAllCountry" resultType="com.mybatis.read.Country">
        SELECT * FROM oaxt.country
    </select>
</mapper>

Configuration中使用caches来管理每个namespace对应的Cache对象:

protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");

XMLMapperBuilder解析mapper的cache结点:

private void cacheElement(XNode context) {
    if (context != null) {
        String type = context.getStringAttribute("type", "PERPETUAL");
        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);
    }
}

二级缓存的读取和放入是在CachingExecutor的TransactionalCacheManager对象完成的:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
			ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 在mapper.xml中配置了cache结点后,这里将取到不为空的cache对象
    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;
        }
    }
    // 将在BaseExecutor中使用二级缓存
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

以图片的形式描述上述过程:

需要注意的是,在使用二级缓存过程中,只有执行了commit操作才会真正的将需要缓存的对象存入缓存结构中,TransactionCache的putObject方法只是简单的执行了entriesToAddOnCommit.put(key, object)操作,只有commit时才会调用实际缓存对象的putObject方法:

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);
        }
    }
}

缓存构建中的装饰器模式

以Cache接口为基础,以PerpetualCache为基本实现,定义了ScheduledCache、LoggingCache等实现了不同功能的cache组,使用装饰器模式进行不同形式的组合实现功能的拼装。在CacheBuilder中可以看到构造多重功能复合的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);
        }
        if (readWrite) {
            cache = new SerializedCache(cache);
        }
        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);
    }
}

自定义Cache实现

myBatis提供的缓存的基础实现是在堆内存上HashMap缓存数据,当数据量大时会占据大量堆内存,降低应用的整体性能。可以利用其提供的Cache接口,在二级缓存中进行自定义实现Cache,将数据缓存到Redis等外部存储中,更好的发挥缓存的作用。

CacheBuilder的builde方法是能够进行自定义Cache实现的基础:

public Cache build() {
    setDefaultImplementations();
    // 这里的implementatin解析的是mapper.xml配置中的cache结点的type属性
    // 当不传时,默认是PerpetualCache
    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都提供了集成:

缓存插件cache类名github地址document地址
RedisRedisCachehttps://github.com/mybatis/redis-cachemybatis.org/redis-cache/
EhCacheEhcacheCachehttps://github.com/mybatis/ehcache-cachemybatis.org/ehcache-cache/
memcachedMemcachedCachehttps://github.com/mybatis/memcached-cachemybatis.org/memcached-cache/
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值